diff --git a/extension-manifest-v2/package.json b/extension-manifest-v2/package.json index eb922c1f3..bb25e7dc2 100644 --- a/extension-manifest-v2/package.json +++ b/extension-manifest-v2/package.json @@ -47,7 +47,6 @@ "@ghostery/trackers-preview": "^1.0.0", "@ghostery/ui": "^1.0.0", "@sentry/browser": "7.1.1", - "@whotracksme/webextension-packages": "^4.0.1", "classnames": "^2.3.2", "d3": "^5.16.0", "foundation-sites": "^6.6.2", diff --git a/extension-manifest-v3/package.json b/extension-manifest-v3/package.json index 579e28067..18112e3e4 100644 --- a/extension-manifest-v3/package.json +++ b/extension-manifest-v3/package.json @@ -39,7 +39,7 @@ "@ghostery/trackers-preview": "^1.0.0", "@ghostery/ui": "^1.0.0", "@github/relative-time-element": "^4.1.5", - "@whotracksme/webextension-packages": "^4.0.3", + "@whotracksme/webextension-packages": "^4.0.5", "hybrids": "^8.2.4", "idb": "^7.1.1", "jwt-decode": "^3.1.2", diff --git a/extension-manifest-v3/src/background/adblocker.js b/extension-manifest-v3/src/background/adblocker.js index 0d038d79f..7328f2a64 100644 --- a/extension-manifest-v3/src/background/adblocker.js +++ b/extension-manifest-v3/src/background/adblocker.js @@ -21,7 +21,7 @@ import Request from './utils/request.js'; import asyncSetup from './utils/setup.js'; import * as engines from './utils/engines.js'; -import { setupTabStats, updateTabStats } from './stats.js'; +import { updateTabStats } from './stats.js'; let enabledEngines = []; let pausedDomains = []; @@ -333,32 +333,23 @@ function isPaused(details, request) { if (__PLATFORM__ === 'firefox') { chrome.webRequest.onBeforeRequest.addListener( (details) => { + if (details.tabId < 0) return; + const request = Request.fromRequestDetails(details); - const tabId = details.tabId; - // Update stats - if (tabId > -1 && (request.sourceDomain || request.sourceHostname)) { - if (request.isMainFrame() && !request.isHttp && !request.isHttps) { - return; + // INFO: request.source... is only available in Firefox + if (request.sourceDomain || request.sourceHostname) { + if (details.type !== 'main_frame') { + updateTabStats(details.tabId, [request]); } - Promise.resolve().then( - request.isMainFrame() - ? () => - setupTabStats( - tabId, - request.sourceDomain || request.sourceHostname, - ) - : () => updateTabStats(tabId, [request]), - ); - if (isPaused(details, request)) return; for (const name of enabledEngines) { const engine = engines.get(name); if (!engine) continue; - if (request.isMainFrame()) { + if (details.type === 'main_frame') { const htmlFilters = engine.getHtmlFilters(request); if (htmlFilters.length !== 0) { filterRequestHTML( diff --git a/extension-manifest-v3/src/background/reporting/index.js b/extension-manifest-v3/src/background/reporting/index.js index f2b63d71f..f10784330 100644 --- a/extension-manifest-v3/src/background/reporting/index.js +++ b/extension-manifest-v3/src/background/reporting/index.js @@ -137,11 +137,13 @@ if (__PLATFORM__ === 'firefox') { if (event === 'observed') { return; } - const request = Request.fromRawDetails({ + + const request = Request.fromRequestDetails({ url: state.url, - sourceUrl: state.tabUrl, + originUrl: state.tabUrl, }); request.modified = true; + updateTabStats(state.tabId, [request]); }, }); diff --git a/extension-manifest-v3/src/background/stats.js b/extension-manifest-v3/src/background/stats.js index 7daf17aeb..ca527cca6 100644 --- a/extension-manifest-v3/src/background/stats.js +++ b/extension-manifest-v3/src/background/stats.js @@ -9,7 +9,6 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0 */ -import { parse } from 'tldts-experimental'; import { store } from 'hybrids'; import { getOffscreenImageData } from '@ghostery/ui/wheel'; @@ -50,22 +49,22 @@ async function refreshIcon(tabId) { if (!stats) return; const options = await store.resolve(Options); - if (options.trackerWheel && stats.trackers.length > 0) { - const paused = options.paused?.some(({ id }) => id === stats.domain); - const data = {}; - - if (paused || !options.terms) { - data.path = { - 16: '/assets/images/icon19_off.png', - 32: '/assets/images/icon38_off.png', - }; - } else { - data.imageData = getOffscreenImageData( - 128, - stats.trackers.map((t) => t.category), - ); - } + const paused = options.paused?.some(({ id }) => id === stats.domain); + const data = {}; + + if (paused || !options.terms) { + data.path = { + 16: '/assets/images/icon19_off.png', + 32: '/assets/images/icon38_off.png', + }; + } else if (options.trackerWheel && stats.trackers.length > 0) { + data.imageData = getOffscreenImageData( + 128, + stats.trackers.map((t) => t.category), + ); + } + if (data.path || data.imageData) { // Note: Even in MV3, this is not (yet) returning a promise. chromeAction.setIcon({ tabId, ...data }, () => { if (chrome.runtime.lastError) { @@ -95,91 +94,80 @@ const delayMap = new Map(); function updateIcon(tabId) { if (delayMap.has(tabId)) return; - const timeoutId = setTimeout( - () => { - delayMap.delete(tabId); - refreshIcon(tabId); - }, - // Firefox flickers when updating the icon, so we should expand the throttle - __PLATFORM__ === 'firefox' ? 1000 : 250, + delayMap.set( + tabId, + setTimeout( + () => { + delayMap.delete(tabId); + refreshIcon(tabId); + }, + // Firefox flickers when updating the icon, so we should expand the throttle + __PLATFORM__ === 'firefox' ? 1000 : 250, + ), ); - delayMap.set(tabId, timeoutId); refreshIcon(tabId); } -updateIcon.cancel = (tabId) => { - const timeoutId = delayMap.get(tabId); - if (timeoutId) { - clearTimeout(timeoutId); - delayMap.delete(tabId); - } -}; - -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 }); -} +export function updateTabStats(tabId, requests) { + Promise.resolve().then(async () => { + const stats = tabStats.get(tabId); -export async function updateTabStats(tabId, requests) { - const stats = tabStats.get(tabId); + // Stats might not be available on Firefox using webRequest.onBeforeRequest + // as some of the requests are fired before the tab is created, tabId -1 + if (!stats) return; - // Stats might not be available on Firefox using webRequest.onBeforeRequest - // as some of the requests are fired before the tab is created, tabId -1 - if (!stats) return; + let sortingRequired = false; - for (const request of requests) { - const pattern = await trackerDb.getMetadata(request, { - getDomainMetadata: true, - }); + // Filter out requests that are not related to the current page + // (e.g. requests on trailing edge when navigation to a new page is in progress) + requests = requests.filter((request) => request.isFromOriginUrl(stats.url)); - if (pattern) { - let tracker = stats.trackers.find((t) => t.id === pattern.id); + for (const request of requests) { + const pattern = await trackerDb.getMetadata(request, { + getDomainMetadata: true, + }); - if (!tracker) { - tracker = { ...pattern, requests: [] }; - stats.trackers.push(tracker); - } + if (pattern || request.blocked || request.modified) { + let tracker = + (pattern && stats.trackers.find((t) => t.id === pattern.id)) || + stats.trackers.find((t) => t.id === request.domain); + + if (!tracker) { + tracker = pattern + ? { ...pattern, requests: [] } + : { + id: request.domain, + name: request.domain, + category: 'unidentified', + requests: [], + }; + + stats.trackers.push(tracker); + sortingRequired = true; + } - tracker.requests.push({ - url: request.url, - blocked: request.blocked || false, - modified: request.modified || false, - }); - } else if (request.blocked || request.modified) { - let tracker = stats.trackers.find((t) => t.id === request.domain); - - if (!tracker) { - tracker = { - id: request.domain, - name: request.domain, - category: 'unidentified', - requests: [], - }; - stats.trackers.push(tracker); + tracker.requests.push({ + id: request.requestId, + url: request.url, + blocked: request.blocked, + modified: request.modified, + }); } - - tracker.requests.push({ - url: request.url, - blocked: request.blocked || false, - modified: request.modified || false, - }); } - } - stats.trackers.sort( - (a, b) => order.indexOf(a.category) - order.indexOf(b.category), - ); + if (sortingRequired) { + stats.trackers.sort( + (a, b) => order.indexOf(a.category) - order.indexOf(b.category), + ); + } - tabStats.set(tabId, stats); - updateIcon(tabId); + // After navigation stats are cleared, so the current `stats` variable might be outdated + if (stats === tabStats.get(tabId)) { + tabStats.set(tabId, stats); + updateIcon(tabId); + } + }); } const DAILY_STATS_ADS_CATEGORY = 'advertising'; @@ -235,45 +223,69 @@ async function flushTabStatsToDailyStats(tabId) { }); } -export function setupTabStats(tabId, domain) { +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); +}); + +function setupTabStats(tabId, request) { flushTabStatsToDailyStats(tabId); - if (domain) { + if (request.isHttp || request.isHttps) { tabStats.set(tabId, { - domain, - requestsBlocked: [], + url: request.url, + domain: request.domain || request.hostname, trackers: [], }); - - // Clean up icon update - updateIcon.cancel(tabId); } else { tabStats.delete(tabId); } + + updateIcon(tabId); } -chrome.tabs.onRemoved.addListener((tabId) => { - flushTabStatsToDailyStats(tabId); - tabStats.delete(tabId); +// Setup stats for the tab when a user navigates to a new page +chrome.webNavigation.onBeforeNavigate.addListener((details) => { + if (details.tabId < 0) return; + + if (details.parentFrameId === -1) { + setupTabStats(details.tabId, Request.fromRequestDetails(details)); + } }); if (__PLATFORM__ === 'safari') { + // On Safari we have content script to sends back the list of urls chrome.runtime.onMessage.addListener((msg, sender) => { - if (sender.url && sender.frameId !== undefined && sender.tab?.id) { + if (sender.url && sender.frameId !== undefined && sender.tab?.id > -1) { switch (msg.action) { - // We cannot trust that Safari fires "chrome.webNavigation.onCommitted" - // with the correct tabId (sometimes it is correct, sometimes it is 0). - // Thus, let the content_script fire it. - case 'onCommitted': { - const { domain, hostname } = parse(sender.url); - setupTabStats(sender.tab.id, domain || hostname); - break; - } case 'updateTabStats': + // On Safari `onBeforeNavigate` event is not trigger correctly + // if a user navigates to the page by setting URL in the address bar. + // This condition ensures that we setup tabStats for the tab + // when `updateTabStats` message is received. + if (tabStats.get(sender.tab.id)?.url !== sender.url) { + setupTabStats( + sender.tab.id, + Request.fromRequestDetails({ url: sender.url }), + ); + } + updateTabStats( sender.tab.id, msg.urls.map((url) => - Request.fromRawDetails({ url, sourceUrl: sender.url }), + Request.fromRequestDetails({ url, originUrl: sender.url }), ), ); break; @@ -284,41 +296,38 @@ if (__PLATFORM__ === 'safari') { }); } -// Following code only applies to chromium-based browsers excluding: -// * Safari - it does not support chrome.webRequest.onBeforeRequest -// * Firefox - it has own implementation in `./adblocker.js` with blocking requests if (__PLATFORM__ !== 'safari' && __PLATFORM__ !== 'firefox') { + // Chromium based browser can use readonly webRequest API chrome.webRequest.onBeforeRequest.addListener( (details) => { if (details.tabId < 0) return; const request = Request.fromRequestDetails(details); - - if (request.isMainFrame() && !request.isHttp && !request.isHttps) { - return; + if (details.type !== 'main_frame') { + updateTabStats(details.tabId, [request]); } - - Promise.resolve().then( - request.isMainFrame() - ? () => - setupTabStats( - details.tabId, - request.sourceDomain || request.sourceHostname, - ) - : () => updateTabStats(details.tabId, [request]), - ); }, { urls: [''], }, ); + // Callback for listing if requests were blocked by the DNR chrome.webRequest.onErrorOccurred.addListener( (details) => { - if (details.error !== 'net::ERR_BLOCKED_BY_CLIENT') return; - - const stats = tabStats.get(details.tabId); - stats?.requestsBlocked.push(details.requestId); + if (details.error === 'net::ERR_BLOCKED_BY_CLIENT') { + const stats = tabStats.get(details.tabId); + if (!stats) return; + + for (const tracker of stats.trackers) { + for (const request of tracker.requests) { + if (request.id === details.requestId) { + request.blocked = true; + return; + } + } + } + } }, { urls: [''], diff --git a/extension-manifest-v3/src/background/utils/request.js b/extension-manifest-v3/src/background/utils/request.js index 2df357c0f..c74a22fac 100644 --- a/extension-manifest-v3/src/background/utils/request.js +++ b/extension-manifest-v3/src/background/utils/request.js @@ -28,11 +28,10 @@ export default class ExtendedRequest extends Request { domain: parsedUrl.domain || '', hostname: parsedUrl.hostname || '', - url: details.url.toLowerCase(), + url: details.url, sourceDomain: parsedSourceUrl.domain || '', sourceHostname: parsedSourceUrl.hostname || '', - sourceUrl: sourceUrl.toLowerCase(), type: details.type, @@ -44,8 +43,38 @@ export default class ExtendedRequest extends Request { super(data); this.requestId = data.requestId; + this.blocked = false; + this.modified = false; + this.sourceDomain = data.sourceDomain; this.sourceHostname = data.sourceHostname; } + + isFromOriginUrl(url) { + const { frameAncestors } = this._originalRequestDetails; + + /* Firefox APIs */ + + if (frameAncestors && frameAncestors.length > 0) { + return url === frameAncestors[frameAncestors.length - 1].url; + } + + if (this.sourceUrl) { + return url === this.sourceUrl; + } + + /* Chrome APIs */ + + const { frameType, initiator } = this._originalRequestDetails; + + // For frameType 'sub_frame', we can't determine the origin URL + // as it might be the iframe itself or any of its ancestors + if (frameType === 'outermost_frame' && initiator) { + return url.startsWith(initiator); + } + + // As a fallback, we assume that the request is from the origin URL + return true; + } } diff --git a/extension-manifest-v3/src/background/utils/trackerdb.js b/extension-manifest-v3/src/background/utils/trackerdb.js index e68af9dd0..3571bb94f 100644 --- a/extension-manifest-v3/src/background/utils/trackerdb.js +++ b/extension-manifest-v3/src/background/utils/trackerdb.js @@ -49,6 +49,8 @@ export async function getMetadata(request) { const patterns = new Map(); export async function getPattern(key) { + if (promise) await promise; + const engine = engines.get('trackerdb'); if (!engine) return null; diff --git a/extension-manifest-v3/src/content_scripts/whotracksme/index.js b/extension-manifest-v3/src/content_scripts/whotracksme/index.js index 372576437..1725a7591 100644 --- a/extension-manifest-v3/src/content_scripts/whotracksme/index.js +++ b/extension-manifest-v3/src/content_scripts/whotracksme/index.js @@ -16,12 +16,6 @@ function postMessage({ urls }) { chrome.runtime.sendMessage({ action: 'updateTabStats', urls }); } -// Should only be needed on Safari: -// the tabId of the initial chrome.webNavigation.onCommitted -// is not reliable. When opening bookmarks, it can happen that -// the event is associated with a tabId of 0. -chrome.runtime.sendMessage({ action: 'onCommitted' }); - // Based on https://github.com/mozilla-mobile/firefox-ios/blob/1f3fd1640214b2b442c573ea7d2882d480f4f24c/content-blocker-lib-ios/js/TrackingProtectionStats.js /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this diff --git a/extension-manifest-v3/src/manifest.chromium.json b/extension-manifest-v3/src/manifest.chromium.json index 2037f9b3e..3fca878e8 100644 --- a/extension-manifest-v3/src/manifest.chromium.json +++ b/extension-manifest-v3/src/manifest.chromium.json @@ -67,14 +67,14 @@ }, "content_scripts": [ { - "matches": ["*://*/*"], + "matches": ["http://*/*", "https://*/*"], "run_at": "document_start", "js": [ "content_scripts/adblocker.js" ] }, { - "matches": ["*://*/*"], + "matches": ["http://*/*", "https://*/*"], "run_at": "document_start", "js": [ "content_scripts/autoconsent.js" @@ -82,14 +82,14 @@ "all_frames": true }, { - "matches": ["*://*/*"], + "matches": ["http://*/*", "https://*/*"], "run_at": "document_start", "js": [ "content_scripts/iframe.js" ] }, { - "matches": ["*://*/*"], + "matches": ["http://*/*", "https://*/*"], "run_at": "document_start", "js": [ "content_scripts/whotracksme/reporting.js" diff --git a/extension-manifest-v3/src/manifest.firefox.json b/extension-manifest-v3/src/manifest.firefox.json index c673e7331..c3066fcf2 100644 --- a/extension-manifest-v3/src/manifest.firefox.json +++ b/extension-manifest-v3/src/manifest.firefox.json @@ -47,14 +47,14 @@ }, "content_scripts": [ { - "matches": ["*://*/*"], + "matches": ["http://*/*", "https://*/*"], "run_at": "document_start", "js": [ "content_scripts/adblocker.js" ] }, { - "matches": ["*://*/*"], + "matches": ["http://*/*", "https://*/*"], "run_at": "document_start", "js": [ "content_scripts/autoconsent.js" @@ -62,14 +62,14 @@ "all_frames": true }, { - "matches": ["*://*/*"], + "matches": ["http://*/*", "https://*/*"], "run_at": "document_start", "js": [ "content_scripts/iframe.js" ] }, { - "matches": ["*://*/*"], + "matches": ["http://*/*", "https://*/*"], "run_at": "document_start", "js": [ "content_scripts/whotracksme/reporting.js" diff --git a/extension-manifest-v3/src/manifest.safari.json b/extension-manifest-v3/src/manifest.safari.json index d19ae5f6c..bf5250db5 100644 --- a/extension-manifest-v3/src/manifest.safari.json +++ b/extension-manifest-v3/src/manifest.safari.json @@ -66,21 +66,21 @@ }, "content_scripts": [ { - "matches": ["*://*/*"], + "matches": ["http://*/*", "https://*/*"], "run_at": "document_start", "js": [ "content_scripts/whotracksme/index.js" ] }, { - "matches": ["*://*/*"], + "matches": ["http://*/*", "https://*/*"], "run_at": "document_start", "js": [ "content_scripts/adblocker.js" ] }, { - "matches": ["*://*/*"], + "matches": ["http://*/*", "https://*/*"], "run_at": "document_start", "js": [ "content_scripts/autoconsent.js" @@ -88,14 +88,14 @@ "all_frames": true }, { - "matches": ["*://*/*"], + "matches": ["http://*/*", "https://*/*"], "run_at": "document_start", "js": [ "content_scripts/iframe.js" ] }, { - "matches": ["*://*/*"], + "matches": ["http://*/*", "https://*/*"], "run_at": "document_start", "js": [ "content_scripts/whotracksme/reporting.js" diff --git a/extension-manifest-v3/src/pages/panel/index.js b/extension-manifest-v3/src/pages/panel/index.js index d0bcc851d..3ff65fbb3 100644 --- a/extension-manifest-v3/src/pages/panel/index.js +++ b/extension-manifest-v3/src/pages/panel/index.js @@ -65,6 +65,8 @@ if (__PLATFORM__ === 'firefox') { let el = event.target; while (el && !el.href) el = el.parentElement; + if (!el) return; + // Timeout is required to prevent from closing the window before the anchor is opened if (el.origin !== location.origin || el.pathname !== location.pathname) { setTimeout(window.close, 100); diff --git a/package-lock.json b/package-lock.json index b41d3468a..f30bc6f2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,6 @@ "@ghostery/trackers-preview": "^1.0.0", "@ghostery/ui": "^1.0.0", "@sentry/browser": "7.1.1", - "@whotracksme/webextension-packages": "^4.0.1", "classnames": "^2.3.2", "d3": "^5.16.0", "foundation-sites": "^6.6.2", @@ -118,7 +117,7 @@ "@ghostery/trackers-preview": "^1.0.0", "@ghostery/ui": "^1.0.0", "@github/relative-time-element": "^4.1.5", - "@whotracksme/webextension-packages": "^4.0.3", + "@whotracksme/webextension-packages": "^4.0.5", "hybrids": "^8.2.4", "idb": "^7.1.1", "jwt-decode": "^3.1.2", @@ -3440,9 +3439,9 @@ } }, "node_modules/@whotracksme/webextension-packages": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@whotracksme/webextension-packages/-/webextension-packages-4.0.3.tgz", - "integrity": "sha512-KCtWFHzg92ujEryb7I0JRjRiFkFHIvEHZupltVBlmwxUvwGK8bNcUDbjrkm6OsH7uHHNFJBKOPaGlyGBTBwFgw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@whotracksme/webextension-packages/-/webextension-packages-4.0.5.tgz", + "integrity": "sha512-hnmWqeerrgNzwgQwKAGx9G1/q2LlL1d7A0aX/qzlPGbGg80/a6LC/ySlLAHw0vpSw8CmDkL8ejmAWV9UISOqIA==", "dependencies": { "@cliqz/url-parser": "^1.1.5", "date-fns": "^2.29.3", @@ -7525,13 +7524,13 @@ } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", "dev": true, "peer": true, "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -17026,9 +17025,9 @@ } }, "node_modules/resolve": { - "version": "1.22.6", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", - "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { "is-core-module": "^2.13.0", diff --git a/packages/ui/src/modules/panel/components/stats.js b/packages/ui/src/modules/panel/components/stats.js index 2a4ab9a0b..d0b58fb0f 100644 --- a/packages/ui/src/modules/panel/components/stats.js +++ b/packages/ui/src/modules/panel/components/stats.js @@ -51,7 +51,6 @@ export default { ), }, domain: '', - wtmUrl: ({ domain }) => `https://www.whotracks.me/websites/${domain}.html`, type: { value: 'graph', observe(host, value, lastValue) { @@ -66,7 +65,7 @@ export default { categories, categoryList, trackers, - wtmUrl, + domain, type, dialog, label, @@ -83,12 +82,16 @@ export default { - ${wtmUrl && + ${domain && html` WhoTracks.Me Statistical Report - +