diff --git a/src/background/broken-page-report.js b/src/background/broken-page-report.js
new file mode 100644
index 000000000..8dae8ba64
--- /dev/null
+++ b/src/background/broken-page-report.js
@@ -0,0 +1,104 @@
+/**
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2017-present Ghostery GmbH. All rights reserved.
+ *
+ * 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+import { store } from 'hybrids';
+
+import Options, { SYNC_OPTIONS } from '/store/options.js';
+
+import getBrowserInfo from '/utils/browser-info.js';
+import { SUPPORT_PAGE_URL } from '/utils/urls.js';
+
+import { tabStats } from './stats.js';
+
+async function getMetadata(tab) {
+ let result = '\n\n------\n\n';
+
+ const { version } = chrome.runtime.getManifest();
+ result += `Extension version: ${version}`;
+
+ const trackers = tabStats.get(tab.id)?.trackers.map((t) => t.id);
+ if (trackers) {
+ result += `\nTrackers(${trackers.length}): ${trackers.join(', ')}`;
+ }
+
+ // Send only not-private options
+ const options = Object.fromEntries(
+ Object.entries(await store.resolve(Options)).filter(([key]) =>
+ SYNC_OPTIONS.includes(key),
+ ),
+ );
+
+ result += `\nOptions: ${JSON.stringify(options)}`;
+
+ return result;
+}
+
+chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
+ if (msg.action === 'report-broken-page') {
+ (async () => {
+ try {
+ const formData = new FormData();
+ const browserInfo = await getBrowserInfo();
+
+ formData.append('support_ticket[user_name]', '');
+ formData.append('support_ticket[user_email]', msg.email);
+ formData.append(
+ 'support_ticket[subject]',
+ `[GBE] Broken page report: ${msg.url}`,
+ );
+
+ formData.append(
+ 'support_ticket[message]',
+ msg.description + (await getMetadata(msg.tab)),
+ );
+
+ formData.append('support_ticket[selected_browser]', browserInfo.name);
+ formData.append('support_ticket[browser_version]', browserInfo.version);
+
+ if (browserInfo.osVersion !== 'other') {
+ formData.append('support_ticket[selected_os]', browserInfo.osVersion);
+ formData.append('support_ticket[os_version]', '');
+ }
+
+ if (msg.screenshot) {
+ const screenshot = await chrome.tabs.captureVisibleTab(null, {
+ format: 'jpeg',
+ quality: 100,
+ });
+ formData.append(
+ 'support_ticket[screenshot]',
+ await fetch(screenshot).then((res) => res.blob()),
+ 'screenshot.jpeg',
+ );
+ }
+
+ await fetch(SUPPORT_PAGE_URL, {
+ method: 'POST',
+ body: formData,
+ }).then((res) => {
+ if (!res.ok || res.status > 204) {
+ throw new Error(
+ `Sending report has failed with status: ${res.status}`,
+ );
+ }
+ });
+
+ sendResponse();
+ } catch (e) {
+ sendResponse(e.message);
+ }
+ })();
+
+ return true;
+ }
+
+ return false;
+});
diff --git a/src/background/index.js b/src/background/index.js
index b9529cf73..f2e6334b1 100644
--- a/src/background/index.js
+++ b/src/background/index.js
@@ -22,6 +22,7 @@ import './session.js';
import './stats.js';
import './notifications.js';
import './serp.js';
+import './broken-page-report.js';
import './helpers.js';
import './external.js';
diff --git a/src/background/session.js b/src/background/session.js
index 919e40d8c..7d78c3ca2 100644
--- a/src/background/session.js
+++ b/src/background/session.js
@@ -9,7 +9,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0
*/
import { UPDATE_SESSION_ACTION_NAME } from '/store/session.js';
-import { HOME_PAGE_URL, ACCOUNT_PAGE_URL } from '/utils/api.js';
+import { HOME_PAGE_URL, ACCOUNT_PAGE_URL } from '/utils/urls.js';
// Observe cookie changes (login/logout actions)
chrome.webNavigation.onDOMContentLoaded.addListener(async ({ url = '' }) => {
diff --git a/src/background/sync.js b/src/background/sync.js
index 36e1051e5..e12d041ea 100644
--- a/src/background/sync.js
+++ b/src/background/sync.js
@@ -15,7 +15,7 @@ import Session from '/store/session.js';
import { getUserOptions, setUserOptions } from '/utils/api.js';
import * as OptionsObserver from '/utils/options-observer.js';
-import { HOME_PAGE_URL, ACCOUNT_PAGE_URL } from '/utils/api.js';
+import { HOME_PAGE_URL, ACCOUNT_PAGE_URL } from '/utils/urls.js';
import debounce from '/utils/debounce.js';
const syncOptions = debounce(
diff --git a/src/manifest.chromium.json b/src/manifest.chromium.json
index c1ad8589c..0d673b904 100644
--- a/src/manifest.chromium.json
+++ b/src/manifest.chromium.json
@@ -14,6 +14,7 @@
"storage",
"scripting",
"tabs",
+ "activeTab",
"webRequest",
"offscreen"
],
diff --git a/src/manifest.firefox.json b/src/manifest.firefox.json
index cbdcc5066..513ee3eaa 100644
--- a/src/manifest.firefox.json
+++ b/src/manifest.firefox.json
@@ -11,6 +11,7 @@
"storage",
"scripting",
"tabs",
+ "activeTab",
"webNavigation",
"webRequest",
"webRequestBlocking",
diff --git a/src/manifest.safari.json b/src/manifest.safari.json
index 834d85c07..5de06d278 100644
--- a/src/manifest.safari.json
+++ b/src/manifest.safari.json
@@ -13,7 +13,8 @@
"webNavigation",
"storage",
"scripting",
- "tabs"
+ "tabs",
+ "activeTab"
],
"host_permissions": [
"http://*/*",
diff --git a/src/pages/panel/assets/contribution.svg b/src/pages/panel/assets/contribution.svg
new file mode 100644
index 000000000..2c725ac8a
--- /dev/null
+++ b/src/pages/panel/assets/contribution.svg
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/panel/components/pause.js b/src/pages/panel/components/pause.js
index 3555f1188..ead59f1cf 100644
--- a/src/pages/panel/components/pause.js
+++ b/src/pages/panel/components/pause.js
@@ -89,7 +89,6 @@ export default {
html`
`}
-
+
${pauseList &&
html`
+ ${paused?.revokeAt &&
+ html`
+
+ `}
`
: html`
diff --git a/src/pages/panel/views/report-confirm.js b/src/pages/panel/views/report-confirm.js
new file mode 100644
index 000000000..da69850c1
--- /dev/null
+++ b/src/pages/panel/views/report-confirm.js
@@ -0,0 +1,49 @@
+/**
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2017-present Ghostery GmbH. All rights reserved.
+ *
+ * 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+import { html, router } from 'hybrids';
+
+import contributionImage from '../assets/contribution.svg';
+
+export default {
+ render: () => html`
+
+
+
+
+ Report a broken page
+
+
+
+
+
+
+
+
+
+
+
+
+ Many thanks for your report!
+
+
+ Your contribution helps build a more private and safe internet for
+ the entire Ghostery community.
+
+
+
+
+ `,
+};
diff --git a/src/pages/panel/views/report-form.js b/src/pages/panel/views/report-form.js
new file mode 100644
index 000000000..cc12635f9
--- /dev/null
+++ b/src/pages/panel/views/report-form.js
@@ -0,0 +1,168 @@
+/**
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2017-present Ghostery GmbH. All rights reserved.
+ *
+ * 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+import { html, router, store, msg } from 'hybrids';
+
+import Session from '/store/session.js';
+import { getCurrentTab, openTabWithUrl } from '/utils/tabs.js';
+import { SUPPORT_PAGE_URL } from '/utils/urls.js';
+
+import ReportConfirm from './report-confirm.js';
+
+const Form = {
+ url: '',
+ email: '',
+ description: '',
+ screenshot: false,
+ [store.connect]: {
+ async get() {
+ const [currentTab, session] = await Promise.all([
+ getCurrentTab(),
+ store.resolve(Session),
+ ]);
+
+ const url = currentTab && new URL(currentTab.url);
+
+ return {
+ url: url ? `${url.origin}${url.pathname}` : '',
+ email: session.email,
+ };
+ },
+ async set(_, values) {
+ const error = await chrome.runtime.sendMessage({
+ action: 'report-broken-page',
+ tab: await getCurrentTab(),
+ ...values,
+ });
+
+ if (error) throw new Error(error);
+
+ return values;
+ },
+ },
+};
+
+function submit(host, event) {
+ try {
+ router.resolve(
+ event,
+ store.submit(host.form).then(() => store.clear(Form)),
+ );
+ } catch {
+ event.preventDefault();
+ }
+}
+
+export default {
+ form: store(Form, { draft: true }),
+ render: ({ form }) => html`
+
+
+
+
+ Report a broken page
+
+
+
+
+
+
+
+
+ ${store.ready(form) &&
+ html`
+
+ `}
+
+
+ `,
+};
diff --git a/src/pages/settings/components/custom-filters.js b/src/pages/settings/components/custom-filters.js
index e97f4c804..5514801e4 100644
--- a/src/pages/settings/components/custom-filters.js
+++ b/src/pages/settings/components/custom-filters.js
@@ -38,7 +38,7 @@ export default {
render: ({ input, result, disabled }) => html`
-
+
-
+
Website
-
+
-
+
${blocked
? msg`${tracker.name} will be trusted on this website. | A tracker will be trusted on this website.`
diff --git a/src/pages/settings/views/tracker-details.js b/src/pages/settings/views/tracker-details.js
index 1625ac8f7..0c1b10d8e 100644
--- a/src/pages/settings/views/tracker-details.js
+++ b/src/pages/settings/views/tracker-details.js
@@ -16,7 +16,7 @@ import Tracker from '/store/tracker.js';
import { toggleExceptionDomain } from '/store/tracker-exception.js';
import { openTabWithUrl } from '/utils/tabs.js';
-import { WTM_PAGE_URL } from '/utils/api.js';
+import { WTM_PAGE_URL } from '/utils/urls.js';
import { toggleException } from './trackers.js';
import TrackerAddException from './tracker-add-exception.js';
diff --git a/src/pages/settings/views/trackers.js b/src/pages/settings/views/trackers.js
index af2fc9b93..64a4854e2 100644
--- a/src/pages/settings/views/trackers.js
+++ b/src/pages/settings/views/trackers.js
@@ -136,7 +136,7 @@ export default {
>
${category !== '_all' ? msg`Expand` : msg`Collapse`}
-
+
Show all
@@ -149,15 +149,15 @@ export default {
Trusted
-
-
+
+
-
+
${store.ready(categories) &&
diff --git a/src/pages/settings/views/website-details.js b/src/pages/settings/views/website-details.js
index 942add87f..9235e55a2 100644
--- a/src/pages/settings/views/website-details.js
+++ b/src/pages/settings/views/website-details.js
@@ -16,7 +16,7 @@ import Options from '/store/options.js';
import TrackerException from '/store/tracker-exception.js';
import Tracker from '/store/tracker.js';
-import { WTM_PAGE_URL } from '/utils/api.js';
+import { WTM_PAGE_URL } from '/utils/urls.js';
import { hasWTMStats } from '/utils/wtm-stats.js';
import TrackerDetails from './tracker-details.js';
diff --git a/src/pages/settings/views/websites-add.js b/src/pages/settings/views/websites-add.js
index ec02f478f..10081a400 100644
--- a/src/pages/settings/views/websites-add.js
+++ b/src/pages/settings/views/websites-add.js
@@ -54,7 +54,7 @@ export default {
Website
-
+
-
+
Select time frame
-
+
1 day
Always
-
+
diff --git a/src/pages/settings/views/websites.js b/src/pages/settings/views/websites.js
index f30cde474..b71a227a6 100644
--- a/src/pages/settings/views/websites.js
+++ b/src/pages/settings/views/websites.js
@@ -100,14 +100,14 @@ export default {
-
+
-
+
Add
diff --git a/src/ui/components/button.js b/src/ui/components/button.js
index 1aad3944a..53c3390e8 100644
--- a/src/ui/components/button.js
+++ b/src/ui/components/button.js
@@ -92,6 +92,11 @@ export default {
pointer-events: none;
}
+ :host([disabled][type="transparent"]) {
+ background: none;
+ border-color: transparent;
+ }
+
@media (hover: hover) {
:host([type="primary"]:hover) {
background: var(--ui-color-primary-700);
diff --git a/src/pages/settings/components/input.js b/src/ui/components/input.js
similarity index 100%
rename from src/pages/settings/components/input.js
rename to src/ui/components/input.js
diff --git a/src/utils/api.js b/src/utils/api.js
index 4b71dd3dd..f30cd4a10 100644
--- a/src/utils/api.js
+++ b/src/utils/api.js
@@ -27,13 +27,6 @@ const COOKIE_DURATION = 60 * 60 * 24 * 90; // 90 days in seconds
const COOKIE_SHORT_DURATION = 60 * 60; // 1 hour in seconds
let COOKIE_EXPIRATION_DATE_OFFSET = 0;
-export const HOME_PAGE_URL = `https://www.${GHOSTERY_DOMAIN}/`;
-export const SIGNON_PAGE_URL = `https://www.${GHOSTERY_DOMAIN}/signin`;
-export const CREATE_ACCOUNT_PAGE_URL = `https://www.${GHOSTERY_DOMAIN}/register`;
-export const ACCOUNT_PAGE_URL = `https://www.${GHOSTERY_DOMAIN}/account`;
-
-export const WTM_PAGE_URL = `https://www.${GHOSTERY_DOMAIN}/whotracksme/`;
-
if (__PLATFORM__ === 'safari') {
// Safari has two major inconsistency with the specification:
// * for cookies.set() the `expirationDate` is in seconds since 2001-01-01T00:00:00Z (instead of beginning of epoch)
diff --git a/src/utils/browser-info.js b/src/utils/browser-info.js
index 21bedddfc..14d0ab1d6 100644
--- a/src/utils/browser-info.js
+++ b/src/utils/browser-info.js
@@ -65,6 +65,11 @@ function getOS() {
return 'other';
}
+function getOSVersion() {
+ const ua = getUA();
+ return ua.os.version;
+}
+
function isAndroid() {
return getOS() === 'android';
}
@@ -109,6 +114,7 @@ async function getBrowserInfo() {
name: '',
token: '',
os: '',
+ osVersion: '',
version: '',
};
@@ -142,6 +148,7 @@ async function getBrowserInfo() {
// Set OS property
BROWSER_INFO.os = getOS();
+ BROWSER_INFO.osVersion = getOSVersion();
// Set version property
BROWSER_INFO.version = parseInt(getUA().browser.version.toString(), 10); // convert to string for Chrome
diff --git a/src/utils/urls.js b/src/utils/urls.js
index 12fa79a61..5698a8334 100644
--- a/src/utils/urls.js
+++ b/src/utils/urls.js
@@ -12,3 +12,11 @@
import { debugMode } from './debug.js';
export const GHOSTERY_DOMAIN = debugMode ? 'ghosterystage.com' : 'ghostery.com';
+
+export const HOME_PAGE_URL = `https://www.${GHOSTERY_DOMAIN}/`;
+export const SIGNON_PAGE_URL = `https://www.${GHOSTERY_DOMAIN}/signin`;
+export const CREATE_ACCOUNT_PAGE_URL = `https://www.${GHOSTERY_DOMAIN}/register`;
+export const ACCOUNT_PAGE_URL = `https://www.${GHOSTERY_DOMAIN}/account`;
+export const SUPPORT_PAGE_URL = `https://www.${GHOSTERY_DOMAIN}/support`;
+
+export const WTM_PAGE_URL = `https://www.${GHOSTERY_DOMAIN}/whotracksme/`;