зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1525519 - Land Firefox Monitor system add-on into browser/extensions. r=johannh
Differential Revision: https://phabricator.services.mozilla.com/D18996 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
b58acd745d
Коммит
ab6e6b7baa
|
@ -0,0 +1,6 @@
|
|||
<!-- 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/. -->
|
||||
<svg width="28" height="28" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M27.386 22.024L17.48 2.212a4 4 0 0 0-7.156 0L.418 22.032a4 4 0 0 0 3.578 5.78h19.81a4 4 0 0 0 3.58-5.788zM11.902 7.812a2 2 0 1 1 4 0v8a2 2 0 1 1-4 0v-8zm2 16.5a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5z"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 550 B |
|
@ -0,0 +1,6 @@
|
|||
<!-- 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/. -->
|
||||
<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M27.188 6.714L24 4.874 16.738.684l-.227-.13a3.866 3.866 0 0 0-3.846 0l-.228.13-10.228 5.9-.227.13a3.859 3.859 0 0 0-1.927 3.335V22.382c0 1.372.739 2.646 1.927 3.335l10.449 6.03a1.608 1.608 0 0 0 2.19-.585 1.606 1.606 0 0 0-.584-2.19L3.815 23.071a1.103 1.103 0 0 1-.547-.954V10.314c0-.394.209-.757.547-.954l1.81-1.046 8.418-4.862c.339-.197.757-.19 1.095 0l10.228 5.902c.339.197.548.56.548.954V22.11c0 .394-.21.757-.548.954l-3.458 2-1.748-2.653a8.317 8.317 0 0 0 2.77-6.197c0-4.597-3.742-8.338-8.34-8.338-4.596 0-8.338 3.741-8.338 8.338 0 4.597 3.736 8.339 8.333 8.339.984 0 1.932-.172 2.812-.492l2.652 4.03c.043.062.086.123.136.179.006.012.018.018.03.03.056.062.117.117.179.167.018.012.03.024.05.037.073.055.147.098.227.141.018.006.037.019.055.025.068.03.142.061.216.08.018.006.03.012.049.012.086.025.172.037.258.043.025 0 .05.006.074.006.025 0 .05.006.074.006.05 0 .098-.006.148-.012.024 0 .043 0 .067-.006a1.43 1.43 0 0 0 .271-.062c.025-.006.05-.018.068-.024.067-.025.135-.056.203-.092.012-.007.03-.013.043-.019l4.997-2.886a3.859 3.859 0 0 0 1.926-3.335V10.049a3.885 3.885 0 0 0-1.932-3.335zM9.452 16.215a5.137 5.137 0 0 1 5.133-5.132 5.137 5.137 0 0 1 5.132 5.132 5.137 5.137 0 0 1-5.132 5.133 5.137 5.137 0 0 1-5.133-5.133z" fill="#000" fill-rule="nonzero" fill-opacity=".6"/>
|
||||
</svg>
|
После Ширина: | Высота: | Размер: 1.5 KiB |
|
@ -0,0 +1,9 @@
|
|||
/* 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/. */
|
||||
|
||||
/* eslint-env webextensions */
|
||||
|
||||
"use strict";
|
||||
|
||||
browser.fxmonitor.start();
|
|
@ -0,0 +1,45 @@
|
|||
# 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/.
|
||||
|
||||
# Header of the popup
|
||||
fxmonitor.popupHeader=Have an account on this site?
|
||||
# Firefox Monitor must be treated as a brand, and kept in English.
|
||||
# It cannot be:
|
||||
# - Declined to adapt to grammatical case.
|
||||
# - Transliterated.
|
||||
# - Translated.
|
||||
fxmonitor.brandName=Firefox Monitor
|
||||
# Tooltip text for the popup's anchor icon in the URL bar
|
||||
# %S is replaced with fxmonitor.brandName.
|
||||
fxmonitor.anchorIcon.tooltiptext=Site reported to %S
|
||||
# Text content of popup. Semi-colon list of plural forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# This version is only used when the number of accounts is smaller than 100,000.
|
||||
# The placeholders are:
|
||||
# #1: The exact number of accounts compromised in the breach.
|
||||
# #2: The name of the breached site.
|
||||
# #3: The year of the breach.
|
||||
# #4: The brand name ("Firefox Monitor").
|
||||
fxmonitor.popupText=#1 account from #2 was compromised in #3. Check #4 to see if yours is at risk.;#1 accounts from #2 were compromised in #3. Check #4 to see if yours is at risk.
|
||||
# Text content of popup. Semi-colon list of plural forms.
|
||||
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
|
||||
# This version is only used when the number of accounts is greater than 100,000.
|
||||
# The placeholders are:
|
||||
# #1: The number of accounts compromised in the breach, rounded down to the
|
||||
# most significant digit.
|
||||
# Ex.: 234,567 -> More than 200,000 accounts [...]
|
||||
# 345,678,901 -> More than 300,000,000 accounts [...]
|
||||
# 4,567,890,123 -> More than 4,000,000,000 accounts [...]
|
||||
# #2: The name of the breached site.
|
||||
# #3: The year of the breach.
|
||||
# #4: The brand name ("Firefox Monitor").
|
||||
fxmonitor.popupTextRounded=More than #1 account from #2 was compromised in #3. Check #4 to see if yours is at risk.;More than #1 accounts from #2 were compromised in #3. Check #4 to see if yours is at risk.
|
||||
# %S is replaced with fxmonitor.brandName.
|
||||
fxmonitor.checkButton.label=Check %S
|
||||
fxmonitor.checkButton.accessKey=C
|
||||
fxmonitor.dismissButton.label=Dismiss
|
||||
fxmonitor.dismissButton.accessKey=D
|
||||
# %S is replaced with fxmonitor.brandName.
|
||||
fxmonitor.neverShowButton.label=Never show %S alerts
|
||||
fxmonitor.neverShowButton.accessKey=N
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Firefox Monitor",
|
||||
"version": "3.0",
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "fxmonitor@mozilla.org",
|
||||
"strict_min_version": "65.0"
|
||||
}
|
||||
},
|
||||
"background": {
|
||||
"scripts": ["background.js"]
|
||||
},
|
||||
"experiment_apis": {
|
||||
"fxmonitor": {
|
||||
"schema": "./privileged/schema.json",
|
||||
"parent": {
|
||||
"scopes": ["addon_parent"],
|
||||
"script": "./privileged/api.js",
|
||||
"paths": [["fxmonitor"]]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
|
||||
DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
|
||||
|
||||
FINAL_TARGET_FILES.features['fxmonitor@mozilla.org'] += [
|
||||
'background.js',
|
||||
'manifest.json'
|
||||
]
|
||||
|
||||
FINAL_TARGET_FILES.features['fxmonitor@mozilla.org']['assets'] += [
|
||||
'assets/alert.svg',
|
||||
'assets/monitor32.svg'
|
||||
]
|
||||
|
||||
FINAL_TARGET_FILES.features['fxmonitor@mozilla.org']['privileged'] += [
|
||||
'privileged/api.js',
|
||||
'privileged/FirefoxMonitor.css',
|
||||
'privileged/FirefoxMonitor.jsm',
|
||||
'privileged/schema.json'
|
||||
]
|
||||
|
||||
FINAL_TARGET_FILES.features['fxmonitor@mozilla.org']['privileged']['subscripts'] += [
|
||||
'privileged/subscripts/EveryWindow.jsm',
|
||||
'privileged/subscripts/Globals.jsm',
|
||||
'privileged/subscripts/PanelUI.jsm'
|
||||
]
|
||||
|
||||
FINAL_TARGET_FILES.features['fxmonitor@mozilla.org']['locale']['en-US'] += [
|
||||
'locale/en-US/strings.properties'
|
||||
]
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Firefox', 'Firefox Monitor')
|
|
@ -0,0 +1,90 @@
|
|||
/* 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/. */
|
||||
|
||||
#fxmonitor-notification popupnotificationcontent {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#fxmonitor-notification .popup-notification-body > :not(popupnotificationcontent) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fxmonitor-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
#fxmonitor-notification-anchor,
|
||||
.fxmonitor-icon {
|
||||
animation-timing-function: linear;
|
||||
animation-duration: 0.66s;
|
||||
}
|
||||
|
||||
/* We only want to animate the icon/doorhanger the first time it's shown for a site.
|
||||
An attribute fxmonitoranimationdone is used to control this from FirefoxMonitor.jsm */
|
||||
#fxmonitor-notification-anchor:not([fxmonitoranimationdone]) {
|
||||
animation-name: fxmonitor-anchor-animation;
|
||||
}
|
||||
|
||||
#fxmonitor-notification-anchor:not([fxmonitoranimationdone]):-moz-locale-dir(rtl) {
|
||||
animation-name: fxmonitor-anchor-animation-rtl;
|
||||
}
|
||||
|
||||
#fxmonitor-notification-anchor:not([fxmonitoranimationdone]) .fxmonitor-icon {
|
||||
animation-name: fxmonitor-icon-animation;
|
||||
}
|
||||
|
||||
#notification-popup[popupid=fxmonitor]:not([fxmonitoranimationdone]) {
|
||||
transition-delay: 0.33s;
|
||||
}
|
||||
|
||||
/* Animate the appearance of the anchor icon: push the other icons to the right. */
|
||||
@keyframes fxmonitor-anchor-animation {
|
||||
from {
|
||||
margin-right: -20px;
|
||||
}
|
||||
50% {
|
||||
margin-right: 0;
|
||||
}
|
||||
to {
|
||||
}
|
||||
}
|
||||
|
||||
/* For RTL locales, push the other icons to the left. */
|
||||
@keyframes fxmonitor-anchor-animation-rtl {
|
||||
from {
|
||||
margin-left: -20px;
|
||||
}
|
||||
50% {
|
||||
margin-left: 0;
|
||||
}
|
||||
to {
|
||||
}
|
||||
}
|
||||
|
||||
/* After the appearance of the anchor box, expand the icon into view */
|
||||
@keyframes fxmonitor-icon-animation {
|
||||
from {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
75% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
to {
|
||||
}
|
||||
}
|
||||
|
||||
#fxmonitor-notification .popupText {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
#fxmonitor-notification .headerText {
|
||||
font-weight: 600;
|
||||
white-space: pre;
|
||||
}
|
|
@ -0,0 +1,415 @@
|
|||
/* 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/. */
|
||||
|
||||
/* globals Services, XPCOMUtils */
|
||||
|
||||
this.FirefoxMonitor = {
|
||||
// Map of breached site host -> breach metadata.
|
||||
domainMap: new Map(),
|
||||
|
||||
// Set of hosts for which the user has already been shown,
|
||||
// and interacted with, the popup.
|
||||
warnedHostsSet: new Set(),
|
||||
|
||||
// The above set is persisted as a JSON string in this pref.
|
||||
kWarnedHostsPref: "extensions.fxmonitor.warnedHosts",
|
||||
|
||||
// Reference to the extension object from the WebExtension context.
|
||||
// Used for getting URIs for resources packaged in the extension.
|
||||
extension: null,
|
||||
|
||||
// Whether we've started observing for the user visiting a breached site.
|
||||
observerAdded: false,
|
||||
|
||||
// loadStrings loads a stringbundle into this property.
|
||||
strings: null,
|
||||
|
||||
// This is here for documentation, will be redefined to a pref getter
|
||||
// using XPCOMUtils.defineLazyPreferenceGetter in init().
|
||||
enabled: null,
|
||||
|
||||
kEnabledPref: "extensions.fxmonitor.enabled",
|
||||
|
||||
kNotificationID: "fxmonitor",
|
||||
|
||||
// This is here for documentation, will be redefined to a pref getter
|
||||
// using XPCOMUtils.defineLazyPreferenceGetter in delayedInit().
|
||||
// The value of this property is used as the URL to which the user
|
||||
// is directed when they click "Check Firefox Monitor".
|
||||
FirefoxMonitorURL: null,
|
||||
kFirefoxMonitorURLPref: "extensions.fxmonitor.FirefoxMonitorURL",
|
||||
kDefaultFirefoxMonitorURL: "https://monitor.firefox.com",
|
||||
|
||||
// This is here for documentation, will be redefined to a pref getter
|
||||
// using XPCOMUtils.defineLazyPreferenceGetter in delayedInit().
|
||||
// The pref stores whether the user has seen a breach alert already.
|
||||
// The value is used in warnIfNeeded.
|
||||
firstAlertShown: null,
|
||||
kFirstAlertShownPref: "extensions.fxmonitor.firstAlertShown",
|
||||
|
||||
disable() {
|
||||
Preferences.set(this.kEnabledPref, false);
|
||||
},
|
||||
|
||||
getURL(aPath) {
|
||||
return this.extension.getURL(aPath);
|
||||
},
|
||||
|
||||
getString(aKey) {
|
||||
return this.strings.GetStringFromName(aKey);
|
||||
},
|
||||
|
||||
getFormattedString(aKey, args) {
|
||||
return this.strings.formatStringFromName(aKey, args, args.length);
|
||||
},
|
||||
|
||||
init(aExtension) {
|
||||
this.extension = aExtension;
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this, "enabled", this.kEnabledPref, false,
|
||||
(pref, oldVal, newVal) => {
|
||||
if (newVal) {
|
||||
this.startObserving();
|
||||
} else {
|
||||
this.stopObserving();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (this.enabled) {
|
||||
this.startObserving();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Used to enforce idempotency of delayedInit. delayedInit is
|
||||
// called in startObserving() to ensure we load our strings, etc.
|
||||
_delayedInited: false,
|
||||
async delayedInit() {
|
||||
if (this._delayedInited) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* globals Preferences, RemoteSettings, fetch, btoa, XUL_NS */
|
||||
Services.scriptloader.loadSubScript(
|
||||
this.getURL("privileged/subscripts/Globals.jsm"));
|
||||
|
||||
/* globals EveryWindow */
|
||||
Services.scriptloader.loadSubScript(
|
||||
this.getURL("privileged/subscripts/EveryWindow.jsm"));
|
||||
|
||||
/* globals PanelUI */
|
||||
Services.scriptloader.loadSubScript(
|
||||
this.getURL("privileged/subscripts/PanelUI.jsm"));
|
||||
|
||||
Services.telemetry.registerEvents("fxmonitor", {
|
||||
"interaction": {
|
||||
methods: ["interaction"],
|
||||
objects: [
|
||||
"doorhanger_shown",
|
||||
"doorhanger_removed",
|
||||
"check_btn",
|
||||
"dismiss_btn",
|
||||
"never_show_btn",
|
||||
],
|
||||
// Disabled for now, pending data review (bug 1525977)
|
||||
record_on_release: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Disabled for now, pending data review (bug 1525977)
|
||||
Services.telemetry.setEventRecordingEnabled("fxmonitor", false);
|
||||
|
||||
let warnedHostsJSON = Preferences.get(this.kWarnedHostsPref, "");
|
||||
if (warnedHostsJSON) {
|
||||
try {
|
||||
let json = JSON.parse(warnedHostsJSON);
|
||||
this.warnedHostsSet = new Set(json);
|
||||
} catch (ex) {
|
||||
// Invalid JSON, invalidate the pref.
|
||||
Preferences.reset(this.kWarnedHostsPref);
|
||||
}
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "FirefoxMonitorURL",
|
||||
this.kFirefoxMonitorURLPref, this.kDefaultFirefoxMonitorURL);
|
||||
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "firstAlertShown",
|
||||
this.kFirstAlertShownPref, false);
|
||||
|
||||
await this.loadStrings();
|
||||
await this.loadBreaches();
|
||||
|
||||
this._delayedInited = true;
|
||||
},
|
||||
|
||||
async loadStrings() {
|
||||
// Services.strings.createBundle has a whitelist of URL schemes that it
|
||||
// accepts. moz-extension: is not one of them, so we work around that
|
||||
// by reading the file manually and creating a data: URL (allowed).
|
||||
let response;
|
||||
let locale = Services.locale.defaultLocale;
|
||||
try {
|
||||
response = await fetch(this.getURL(`locale/${locale}/strings.properties`));
|
||||
} catch (e) {
|
||||
Cu.reportError(`Firefox Monitor: no strings available for ${locale}. Falling back to en-US.`);
|
||||
response = await fetch(this.getURL(`locale/en-US/strings.properties`));
|
||||
}
|
||||
let buffer = await response.arrayBuffer();
|
||||
let binary = "";
|
||||
let bytes = new Uint8Array(buffer);
|
||||
let len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
let b64 = btoa(binary);
|
||||
this.strings = Services.strings.createBundle(`data:text/plain;base64,${b64}`);
|
||||
},
|
||||
|
||||
kRemoteSettingsKey: "fxmonitor-breaches",
|
||||
async loadBreaches() {
|
||||
let populateSites = (data) => {
|
||||
this.domainMap.clear();
|
||||
data.forEach(site => {
|
||||
if (!site.Domain || !site.Name || !site.PwnCount || !site.BreachDate || !site.AddedDate) {
|
||||
Cu.reportError(`Firefox Monitor: malformed breach entry.\nSite:\n${JSON.stringify(site)}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.domainMap.set(site.Domain, {
|
||||
Name: site.Name,
|
||||
PwnCount: site.PwnCount,
|
||||
Year: (new Date(site.BreachDate)).getFullYear(),
|
||||
AddedDate: site.AddedDate.split("T")[0],
|
||||
});
|
||||
} catch (e) {
|
||||
Cu.reportError(`Firefox Monitor: malformed breach entry.\nSite:\n${JSON.stringify(site)}\nError:\n${e}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
RemoteSettings(this.kRemoteSettingsKey).on("sync", (event) => {
|
||||
let { data: { current } } = event;
|
||||
populateSites(current);
|
||||
});
|
||||
|
||||
let data = await RemoteSettings(this.kRemoteSettingsKey).get();
|
||||
if (data && data.length) {
|
||||
populateSites(data);
|
||||
}
|
||||
},
|
||||
|
||||
// nsIWebProgressListener implementation.
|
||||
onStateChange(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
|
||||
if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) ||
|
||||
(!aWebProgress.isTopLevel || aWebProgress.isLoadingDocument ||
|
||||
!Components.isSuccessCode(aStatus))) {
|
||||
return;
|
||||
}
|
||||
|
||||
let host;
|
||||
try {
|
||||
host = Services.eTLD.getBaseDomain(aRequest.URI);
|
||||
} catch (e) {
|
||||
// If we can't get the host for the URL, it's not one we
|
||||
// care about for breach alerts anyway.
|
||||
return;
|
||||
}
|
||||
|
||||
this.warnIfNeeded(aBrowser, host);
|
||||
},
|
||||
|
||||
async startObserving() {
|
||||
if (this.observerAdded) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.delayedInit();
|
||||
|
||||
EveryWindow.registerCallback(
|
||||
this.kNotificationID,
|
||||
(win) => {
|
||||
// Inject our stylesheet.
|
||||
let DOMWindowUtils = win.windowUtils;
|
||||
DOMWindowUtils.loadSheetUsingURIString(this.getURL("privileged/FirefoxMonitor.css"),
|
||||
DOMWindowUtils.AUTHOR_SHEET);
|
||||
|
||||
// Set up some helper functions on the window object
|
||||
// for the popup notification to use.
|
||||
win.FirefoxMonitorUtils = {
|
||||
// Keeps track of all notifications currently shown,
|
||||
// so that we can clear them out properly if we get
|
||||
// disabled.
|
||||
notifications: new Set(),
|
||||
disable: () => {
|
||||
this.disable();
|
||||
},
|
||||
getString: (aKey) => {
|
||||
return this.getString(aKey);
|
||||
},
|
||||
getFormattedString: (aKey, args) => {
|
||||
return this.getFormattedString(aKey, args);
|
||||
},
|
||||
getFirefoxMonitorURL: (aSiteName) => {
|
||||
return `${this.FirefoxMonitorURL}/?breach=${encodeURIComponent(aSiteName)}&utm_source=firefox&utm_medium=popup`;
|
||||
},
|
||||
};
|
||||
|
||||
// Setup the popup notification stuff. First, the URL bar icon:
|
||||
let doc = win.document;
|
||||
let notificationBox = doc.getElementById("notification-popup-box");
|
||||
// We create a box to use as the anchor, and put an icon image
|
||||
// inside it. This way, when we animate the icon, its scale change
|
||||
// does not cause the popup notification to bounce due to the anchor
|
||||
// point moving.
|
||||
let anchorBox = doc.createElementNS(XUL_NS, "box");
|
||||
anchorBox.setAttribute("id", `${this.kNotificationID}-notification-anchor`);
|
||||
anchorBox.classList.add("notification-anchor-icon");
|
||||
let img = doc.createElementNS(XUL_NS, "image");
|
||||
img.setAttribute("role", "button");
|
||||
img.classList.add(`${this.kNotificationID}-icon`);
|
||||
img.style.listStyleImage = `url(${this.getURL("assets/monitor32.svg")})`;
|
||||
anchorBox.appendChild(img);
|
||||
notificationBox.appendChild(anchorBox);
|
||||
img.setAttribute("tooltiptext",
|
||||
this.getFormattedString("fxmonitor.anchorIcon.tooltiptext",
|
||||
[this.getString("fxmonitor.brandName")]));
|
||||
|
||||
// Now, the popupnotificationcontent:
|
||||
let parentElt = doc.defaultView.PopupNotifications.panel.parentNode;
|
||||
let pn = doc.createElementNS(XUL_NS, "popupnotification");
|
||||
let pnContent = doc.createElementNS(XUL_NS, "popupnotificationcontent");
|
||||
let panelUI = new PanelUI(doc);
|
||||
pnContent.appendChild(panelUI.box);
|
||||
pn.appendChild(pnContent);
|
||||
pn.setAttribute("id", `${this.kNotificationID}-notification`);
|
||||
pn.setAttribute("hidden", "true");
|
||||
parentElt.appendChild(pn);
|
||||
win.FirefoxMonitorPanelUI = panelUI;
|
||||
|
||||
// Start listening across all tabs!
|
||||
win.gBrowser.addTabsProgressListener(this);
|
||||
},
|
||||
(win) => {
|
||||
// If the window is being destroyed and gBrowser no longer exists,
|
||||
// don't bother doing anything.
|
||||
if (!win.gBrowser) {
|
||||
return;
|
||||
}
|
||||
|
||||
let DOMWindowUtils = win.windowUtils;
|
||||
if (!DOMWindowUtils) {
|
||||
// win.windowUtils was added in 63, fallback if it's not available.
|
||||
DOMWindowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils);
|
||||
}
|
||||
DOMWindowUtils.removeSheetUsingURIString(this.getURL("privileged/FirefoxMonitor.css"),
|
||||
DOMWindowUtils.AUTHOR_SHEET);
|
||||
|
||||
win.FirefoxMonitorUtils.notifications.forEach(n => {
|
||||
n.remove();
|
||||
});
|
||||
delete win.FirefoxMonitorUtils;
|
||||
|
||||
let doc = win.document;
|
||||
doc.getElementById(`${this.kNotificationID}-notification-anchor`).remove();
|
||||
doc.getElementById(`${this.kNotificationID}-notification`).remove();
|
||||
delete win.FirefoxMonitorPanelUI;
|
||||
|
||||
win.gBrowser.removeTabsProgressListener(this);
|
||||
},
|
||||
);
|
||||
|
||||
this.observerAdded = true;
|
||||
},
|
||||
|
||||
stopObserving() {
|
||||
if (!this.observerAdded) {
|
||||
return;
|
||||
}
|
||||
|
||||
EveryWindow.unregisterCallback(this.kNotificationID);
|
||||
|
||||
this.observerAdded = false;
|
||||
},
|
||||
|
||||
warnIfNeeded(browser, host) {
|
||||
if (!this.enabled || this.warnedHostsSet.has(host) || !this.domainMap.has(host)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let site = this.domainMap.get(host);
|
||||
|
||||
// We only alert for breaches that were found up to 2 months ago,
|
||||
// except for the very first alert we show the user - in which case,
|
||||
// we include breaches found in the last three years.
|
||||
let breachDateThreshold = new Date();
|
||||
if (this.firstAlertShown) {
|
||||
breachDateThreshold.setMonth(breachDateThreshold.getMonth() - 2);
|
||||
} else {
|
||||
breachDateThreshold.setFullYear(breachDateThreshold.getFullYear() - 1);
|
||||
}
|
||||
|
||||
if (new Date(site.AddedDate).getTime() < breachDateThreshold.getTime()) {
|
||||
return;
|
||||
} else if (!this.firstAlertShown) {
|
||||
Preferences.set(this.kFirstAlertShownPref, true);
|
||||
}
|
||||
|
||||
this.warnedHostsSet.add(host);
|
||||
Preferences.set(this.kWarnedHostsPref, JSON.stringify([...this.warnedHostsSet]));
|
||||
|
||||
let doc = browser.ownerDocument;
|
||||
let win = doc.defaultView;
|
||||
let panelUI = doc.defaultView.FirefoxMonitorPanelUI;
|
||||
|
||||
let animatedOnce = false;
|
||||
let populatePanel = (event) => {
|
||||
switch (event) {
|
||||
case "showing":
|
||||
panelUI.refresh(site);
|
||||
if (animatedOnce) {
|
||||
// If we've already animated once for this site, don't animate again.
|
||||
doc.getElementById("notification-popup")
|
||||
.setAttribute("fxmonitoranimationdone", "true");
|
||||
doc.getElementById(`${this.kNotificationID}-notification-anchor`)
|
||||
.setAttribute("fxmonitoranimationdone", "true");
|
||||
break;
|
||||
}
|
||||
// Make sure we animate if we're coming from another tab that has
|
||||
// this attribute set.
|
||||
doc.getElementById("notification-popup")
|
||||
.removeAttribute("fxmonitoranimationdone");
|
||||
doc.getElementById(`${this.kNotificationID}-notification-anchor`)
|
||||
.removeAttribute("fxmonitoranimationdone");
|
||||
break;
|
||||
case "shown":
|
||||
animatedOnce = true;
|
||||
break;
|
||||
case "removed":
|
||||
win.FirefoxMonitorUtils.notifications.delete(
|
||||
win.PopupNotifications.getNotification(this.kNotificationID, browser));
|
||||
Services.telemetry.recordEvent("fxmonitor", "interaction", "doorhanger_removed");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
let n = win.PopupNotifications.show(
|
||||
browser, this.kNotificationID, "",
|
||||
`${this.kNotificationID}-notification-anchor`,
|
||||
panelUI.primaryAction, panelUI.secondaryActions, {
|
||||
persistent: true,
|
||||
hideClose: true,
|
||||
eventCallback: populatePanel,
|
||||
popupIconURL: this.getURL("assets/monitor32.svg"),
|
||||
}
|
||||
);
|
||||
|
||||
Services.telemetry.recordEvent("fxmonitor", "interaction", "doorhanger_shown");
|
||||
|
||||
win.FirefoxMonitorUtils.notifications.add(n);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/* 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/. */
|
||||
|
||||
/* globals ExtensionAPI */
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "Services",
|
||||
"resource://gre/modules/Services.jsm");
|
||||
|
||||
let FirefoxMonitorContainer = {};
|
||||
|
||||
this.fxmonitor = class extends ExtensionAPI {
|
||||
getAPI(context) {
|
||||
Services.scriptloader.loadSubScript(context.extension.getURL("privileged/FirefoxMonitor.jsm"),
|
||||
FirefoxMonitorContainer);
|
||||
return {
|
||||
fxmonitor: {
|
||||
async start() {
|
||||
await FirefoxMonitorContainer.FirefoxMonitor.init(context.extension);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
onShutdown(shutdownReason) {
|
||||
if (Services.startup.shuttingDown || !FirefoxMonitorContainer.FirefoxMonitor) {
|
||||
return;
|
||||
}
|
||||
|
||||
FirefoxMonitorContainer.FirefoxMonitor.stopObserving();
|
||||
}
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
[
|
||||
{
|
||||
"namespace": "fxmonitor",
|
||||
"functions": [
|
||||
{
|
||||
"name": "start",
|
||||
"type": "function",
|
||||
"async": true,
|
||||
"parameters": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,62 @@
|
|||
/* 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/. */
|
||||
|
||||
/* globals Services */
|
||||
|
||||
this.EveryWindow = {
|
||||
_callbacks: new Map(),
|
||||
_initialized: false,
|
||||
|
||||
registerCallback: function EW_registerCallback(id, init, uninit) {
|
||||
if (this._callbacks.has(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._callForEveryWindow(init);
|
||||
this._callbacks.set(id, {id, init, uninit});
|
||||
|
||||
if (!this._initialized) {
|
||||
Services.obs.addObserver(this._onOpenWindow.bind(this),
|
||||
"browser-delayed-startup-finished");
|
||||
this._initialized = true;
|
||||
}
|
||||
},
|
||||
|
||||
unregisterCallback: function EW_unregisterCallback(aId, aCallUninit = true) {
|
||||
if (!this._callbacks.has(aId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aCallUninit) {
|
||||
this._callForEveryWindow(this._callbacks.get(aId).uninit);
|
||||
}
|
||||
|
||||
this._callbacks.delete(aId);
|
||||
},
|
||||
|
||||
_callForEveryWindow(aFunction) {
|
||||
let windowList = Services.wm.getEnumerator("navigator:browser");
|
||||
while (windowList.hasMoreElements()) {
|
||||
let win = windowList.getNext();
|
||||
win.delayedStartupPromise.then(() => { aFunction(win); });
|
||||
}
|
||||
},
|
||||
|
||||
_onOpenWindow(aWindow) {
|
||||
for (let c of this._callbacks.values()) {
|
||||
c.init(aWindow);
|
||||
}
|
||||
|
||||
aWindow.addEventListener("unload",
|
||||
this._onWindowClosing.bind(this),
|
||||
{ once: true });
|
||||
},
|
||||
|
||||
_onWindowClosing(aEvent) {
|
||||
let win = aEvent.target;
|
||||
for (let c of this._callbacks.values()) {
|
||||
c.uninit(win);
|
||||
}
|
||||
},
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/* 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/. */
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "Preferences",
|
||||
"resource://gre/modules/Preferences.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "PluralForm",
|
||||
"resource://gre/modules/PluralForm.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "RemoteSettings",
|
||||
"resource://services-settings/remote-settings.js");
|
||||
const {setTimeout, clearTimeout} = ChromeUtils.import("resource://gre/modules/Timer.jsm", {});
|
||||
Cu.importGlobalProperties(["fetch", "btoa"]);
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
|
@ -0,0 +1,117 @@
|
|||
/* 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/. */
|
||||
|
||||
/* globals XUL_NS, Services, PluralForm */
|
||||
|
||||
function PanelUI(doc) {
|
||||
this.site = null;
|
||||
this.doc = doc;
|
||||
|
||||
let box = doc.createElementNS(XUL_NS, "vbox");
|
||||
|
||||
let elt = doc.createElementNS(XUL_NS, "description");
|
||||
elt.textContent = this.getString("fxmonitor.popupHeader");
|
||||
elt.classList.add("headerText");
|
||||
box.appendChild(elt);
|
||||
|
||||
elt = doc.createElementNS(XUL_NS, "description");
|
||||
elt.classList.add("popupText");
|
||||
box.appendChild(elt);
|
||||
|
||||
this.box = box;
|
||||
}
|
||||
|
||||
PanelUI.prototype = {
|
||||
get FirefoxMonitorUtils() {
|
||||
// Set on every window by FirefoxMonitor.jsm for PanelUI to use.
|
||||
// Because sharing is caring.
|
||||
return this.doc.defaultView.FirefoxMonitorUtils;
|
||||
},
|
||||
|
||||
getString(aKey) {
|
||||
return this.FirefoxMonitorUtils.getString(aKey);
|
||||
},
|
||||
|
||||
getFormattedString(aKey, args) {
|
||||
return this.FirefoxMonitorUtils.getFormattedString(aKey, args);
|
||||
},
|
||||
|
||||
get brandString() {
|
||||
if (this._brandString) {
|
||||
return this._brandString;
|
||||
}
|
||||
return this._brandString = this.getString("fxmonitor.brandName");
|
||||
},
|
||||
|
||||
get primaryAction() {
|
||||
if (this._primaryAction) {
|
||||
return this._primaryAction;
|
||||
}
|
||||
return this._primaryAction = {
|
||||
label: this.getFormattedString("fxmonitor.checkButton.label", [this.brandString]),
|
||||
accessKey: this.getString("fxmonitor.checkButton.accessKey"),
|
||||
callback: () => {
|
||||
let win = this.doc.defaultView;
|
||||
win.openTrustedLinkIn(
|
||||
win.FirefoxMonitorUtils.getFirefoxMonitorURL(this.site.Name), "tab", { });
|
||||
|
||||
Services.telemetry.recordEvent("fxmonitor", "interaction", "check_btn");
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
get secondaryActions() {
|
||||
if (this._secondaryActions) {
|
||||
return this._secondaryActions;
|
||||
}
|
||||
return this._secondaryActions = [
|
||||
{
|
||||
label: this.getString("fxmonitor.dismissButton.label"),
|
||||
accessKey: this.getString("fxmonitor.dismissButton.accessKey"),
|
||||
callback: () => {
|
||||
Services.telemetry.recordEvent("fxmonitor", "interaction", "dismiss_btn");
|
||||
},
|
||||
}, {
|
||||
label: this.getFormattedString("fxmonitor.neverShowButton.label", [this.brandString]),
|
||||
accessKey: this.getString("fxmonitor.neverShowButton.accessKey"),
|
||||
callback: () => {
|
||||
this.FirefoxMonitorUtils.disable();
|
||||
Services.telemetry.recordEvent("fxmonitor", "interaction", "never_show_btn");
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
refresh(site) {
|
||||
this.site = site;
|
||||
|
||||
let elt = this.box.querySelector(".popupText");
|
||||
|
||||
// If > 100k, the PwnCount is rounded down to the most significant
|
||||
// digit and prefixed with "More than".
|
||||
// Ex.: 12,345 -> 12,345
|
||||
// 234,567 -> More than 200,000
|
||||
// 345,678,901 -> More than 300,000,000
|
||||
// 4,567,890,123 -> More than 4,000,000,000
|
||||
let k100k = 100000;
|
||||
let pwnCount = site.PwnCount;
|
||||
let stringName = "fxmonitor.popupText";
|
||||
if (pwnCount > k100k) {
|
||||
let multiplier = 1;
|
||||
while (pwnCount >= 10) {
|
||||
pwnCount /= 10;
|
||||
multiplier *= 10;
|
||||
}
|
||||
pwnCount = Math.floor(pwnCount) * multiplier;
|
||||
stringName = "fxmonitor.popupTextRounded";
|
||||
}
|
||||
|
||||
elt.textContent =
|
||||
PluralForm.get(pwnCount, this.getString(stringName))
|
||||
.replace("#1", pwnCount.toLocaleString())
|
||||
.replace("#2", site.Name)
|
||||
.replace("#3", site.Year)
|
||||
.replace("#4", this.brandString);
|
||||
},
|
||||
};
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
DIRS += [
|
||||
'formautofill',
|
||||
'fxmonitor',
|
||||
'pdfjs',
|
||||
'screenshots',
|
||||
'webcompat',
|
||||
|
|
Загрузка…
Ссылка в новой задаче