Backed out 2 changesets (bug 1544875) for Browser-chrome failures in browser/base/content/test/static/browser_all_files_referenced.js
authorDorel Luca <dluca@mozilla.com>
Tue, 23 Apr 2019 17:19:38 +0300
changeset 470494 e639999f1f287997b55013bf92a6ef7d32db7a89
parent 470493 e56e63264f65972f66905dd8893eb549984ffce9
child 470495 8015d85e20f5a3721db7998d7cc3234786eed958
push id35906
push useraciure@mozilla.com
push dateTue, 23 Apr 2019 22:14:56 +0000
treeherdermozilla-central@0ce3633f8b80 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1544875
milestone68.0a1
backs out06b93c66c6e0b3d948e1b225757596a3270d0e51
bb36364808d5854026b816428a81b08c5924e2ef
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 2 changesets (bug 1544875) for Browser-chrome failures in browser/base/content/test/static/browser_all_files_referenced.js Backed out changeset 06b93c66c6e0 (bug 1544875) Backed out changeset bb36364808d5 (bug 1544875)
browser/extensions/fxmonitor/moz.build
browser/extensions/fxmonitor/privileged/FirefoxMonitor.jsm
browser/extensions/fxmonitor/privileged/api.js
browser/extensions/fxmonitor/privileged/subscripts/Globals.jsm
--- a/browser/extensions/fxmonitor/moz.build
+++ b/browser/extensions/fxmonitor/moz.build
@@ -17,13 +17,18 @@ FINAL_TARGET_FILES.features['fxmonitor@m
 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/Globals.jsm'
+]
+
 with Files('**'):
   BUG_COMPONENT = ('Firefox', 'Firefox Monitor')
new file mode 100644
--- /dev/null
+++ b/browser/extensions/fxmonitor/privileged/FirefoxMonitor.jsm
@@ -0,0 +1,521 @@
+/* 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,
+
+  // This is here for documentation, will be redefined to a lazy getter
+  // that creates and returns a string bundle in loadStrings().
+  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",
+
+  // Telemetry event recording is enabled by default.
+  // If this pref exists and is true-y, it's disabled.
+  kTelemetryDisabledPref: "extensions.fxmonitor.telemetryDisabled",
+
+  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, true,
+      (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;
+    }
+
+    this._delayedInited = true;
+
+    /* globals EveryWindow, Preferences, RemoteSettings, fetch, btoa, XUL_NS */
+    Services.scriptloader.loadSubScript(
+      this.getURL("privileged/subscripts/Globals.jsm"));
+
+
+    // Expire our telemetry on November 1, at which time
+    // we should redo data-review.
+    let telemetryExpiryDate = new Date(2019, 10, 1); // Month is zero-index
+    let today = new Date();
+    let expired = today.getTime() > telemetryExpiryDate.getTime();
+
+    Services.telemetry.registerEvents("fxmonitor", {
+      "interaction": {
+        methods: ["interaction"],
+        objects: [
+          "doorhanger_shown",
+          "doorhanger_removed",
+          "check_btn",
+          "dismiss_btn",
+          "never_show_btn",
+        ],
+        record_on_release: true,
+        expired,
+      },
+    });
+
+    let telemetryEnabled = !Preferences.get(this.kTelemetryDisabledPref);
+    Services.telemetry.setEventRecordingEnabled("fxmonitor", telemetryEnabled);
+
+    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();
+  },
+
+  loadStrings() {
+    let l10nManifest;
+    if (this.extension.rootURI instanceof Ci.nsIJARURI) {
+      l10nManifest = this.extension.rootURI.JARFile
+                            .QueryInterface(Ci.nsIFileURL).file;
+    } else if (this.extension.rootURI instanceof Ci.nsIFileURL) {
+      l10nManifest = this.extension.rootURI.file;
+    }
+
+    if (l10nManifest) {
+      Components.manager.addBootstrappedManifestLocation(l10nManifest);
+
+      XPCOMUtils.defineLazyGetter(this, "strings", () => {
+        return Services.strings.createBundle(
+          "chrome://fxmonitor/locale/fxmonitor.properties");
+      });
+    } else {
+      // Something is very strange if we reach this line, so we throw
+      // in order to prevent init from completing and burst the stack.
+      throw new Error("Cannot find fxmonitor chrome.manifest for registering translated strings");
+    }
+  },
+
+  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);
+  },
+
+  notificationsByWindow: new WeakMap(),
+  panelUIsByWindow: new WeakMap(),
+
+  async startObserving() {
+    if (this.observerAdded) {
+      return;
+    }
+
+    EveryWindow.registerCallback(
+      this.kNotificationID,
+      (win) => {
+        if (this.notificationsByWindow.has(win)) {
+          // We've already set up this window.
+          return;
+        }
+
+        this.notificationsByWindow.set(win, new Set());
+
+        // Start listening across all tabs! The UI will
+        // be set up lazily when we actually need to show
+        // a notification.
+        this.delayedInit().then(() => {
+          win.gBrowser.addTabsProgressListener(this);
+        });
+      },
+      (win, closing) => {
+        // If the window is going away, don't bother doing anything.
+        if (closing) {
+          return;
+        }
+
+        let DOMWindowUtils = win.windowUtils;
+        DOMWindowUtils.removeSheetUsingURIString(this.getURL("privileged/FirefoxMonitor.css"),
+                                                 DOMWindowUtils.AUTHOR_SHEET);
+
+        this.notificationsByWindow.get(win).forEach(n => {
+          n.remove();
+        });
+        this.notificationsByWindow.delete(win);
+
+        let doc = win.document;
+        doc.getElementById(`${this.kNotificationID}-notification-anchor`).remove();
+        doc.getElementById(`${this.kNotificationID}-notification`).remove();
+        this.panelUIsByWindow.delete(win);
+
+        win.gBrowser.removeTabsProgressListener(this);
+      },
+    );
+
+    this.observerAdded = true;
+  },
+
+  setupPanelUI(win) {
+    // Inject our stylesheet.
+    let DOMWindowUtils = win.windowUtils;
+    DOMWindowUtils.loadSheetUsingURIString(this.getURL("privileged/FirefoxMonitor.css"),
+                                           DOMWindowUtils.AUTHOR_SHEET);
+
+    // 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);
+    this.panelUIsByWindow.set(win, panelUI);
+    return panelUI;
+  },
+
+  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 = this.panelUIsByWindow.get(win);
+    if (!panelUI) {
+      panelUI = this.setupPanelUI(win);
+    }
+
+    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":
+          this.notificationsByWindow.get(win).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");
+
+    this.notificationsByWindow.get(win).add(n);
+  },
+};
+
+/* globals 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 = {
+  getString(aKey) {
+    return FirefoxMonitor.getString(aKey);
+  },
+
+  getFormattedString(aKey, args) {
+    return FirefoxMonitor.getFormattedString(aKey, args);
+  },
+
+  get brandString() {
+    if (this._brandString) {
+      return this._brandString;
+    }
+    return this._brandString = this.getString("fxmonitor.brandName");
+  },
+
+  getFirefoxMonitorURL: (aSiteName) => {
+    return `${FirefoxMonitor.FirefoxMonitorURL}/?breach=${encodeURIComponent(aSiteName)}&utm_source=firefox&utm_medium=popup`;
+  },
+
+  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(
+          this.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: () => {
+          FirefoxMonitor.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);
+  },
+};
--- a/browser/extensions/fxmonitor/privileged/api.js
+++ b/browser/extensions/fxmonitor/privileged/api.js
@@ -1,547 +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, XPCOMUtils */
+/* globals ExtensionAPI */
 
-ChromeUtils.defineModuleGetter(this, "EveryWindow",
-                               "resource:///modules/EveryWindow.jsm");
-ChromeUtils.defineModuleGetter(this, "PluralForm",
-                               "resource://gre/modules/PluralForm.jsm");
-ChromeUtils.defineModuleGetter(this, "Preferences",
-                               "resource://gre/modules/Preferences.jsm");
-ChromeUtils.defineModuleGetter(this, "RemoteSettings",
-                               "resource://services-settings/remote-settings.js");
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
 
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+let FirefoxMonitorContainer = {};
 
 this.fxmonitor = class extends ExtensionAPI {
   getAPI(context) {
+    Services.scriptloader.loadSubScript(context.extension.getURL("privileged/FirefoxMonitor.jsm"),
+                                        FirefoxMonitorContainer);
     return {
       fxmonitor: {
         async start() {
-          await FirefoxMonitor.init(context.extension);
+          await FirefoxMonitorContainer.FirefoxMonitor.init(context.extension);
         },
       },
     };
   }
 
   onShutdown(shutdownReason) {
-    if (Services.startup.shuttingDown) {
-      return;
-    }
-
-    FirefoxMonitor.stopObserving();
-  }
-};
-
-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,
-
-  // This is here for documentation, will be redefined to a lazy getter
-  // that creates and returns a string bundle in loadStrings().
-  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",
-
-  // Telemetry event recording is enabled by default.
-  // If this pref exists and is true-y, it's disabled.
-  kTelemetryDisabledPref: "extensions.fxmonitor.telemetryDisabled",
-
-  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, true,
-      (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;
-    }
-
-    this._delayedInited = true;
-
-    // Expire our telemetry on November 1, at which time
-    // we should redo data-review.
-    let telemetryExpiryDate = new Date(2019, 10, 1); // Month is zero-index
-    let today = new Date();
-    let expired = today.getTime() > telemetryExpiryDate.getTime();
-
-    Services.telemetry.registerEvents("fxmonitor", {
-      "interaction": {
-        methods: ["interaction"],
-        objects: [
-          "doorhanger_shown",
-          "doorhanger_removed",
-          "check_btn",
-          "dismiss_btn",
-          "never_show_btn",
-        ],
-        record_on_release: true,
-        expired,
-      },
-    });
-
-    let telemetryEnabled = !Preferences.get(this.kTelemetryDisabledPref);
-    Services.telemetry.setEventRecordingEnabled("fxmonitor", telemetryEnabled);
-
-    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();
-  },
-
-  loadStrings() {
-    let l10nManifest;
-    if (this.extension.rootURI instanceof Ci.nsIJARURI) {
-      l10nManifest = this.extension.rootURI.JARFile
-                            .QueryInterface(Ci.nsIFileURL).file;
-    } else if (this.extension.rootURI instanceof Ci.nsIFileURL) {
-      l10nManifest = this.extension.rootURI.file;
-    }
-
-    if (l10nManifest) {
-      Components.manager.addBootstrappedManifestLocation(l10nManifest);
-
-      XPCOMUtils.defineLazyGetter(this, "strings", () => {
-        return Services.strings.createBundle(
-          "chrome://fxmonitor/locale/fxmonitor.properties");
-      });
-    } else {
-      // Something is very strange if we reach this line, so we throw
-      // in order to prevent init from completing and burst the stack.
-      throw new Error("Cannot find fxmonitor chrome.manifest for registering translated strings");
-    }
-  },
-
-  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);
-  },
-
-  notificationsByWindow: new WeakMap(),
-  panelUIsByWindow: new WeakMap(),
-
-  async startObserving() {
-    if (this.observerAdded) {
+    if (Services.startup.shuttingDown || !FirefoxMonitorContainer.FirefoxMonitor) {
       return;
     }
 
-    EveryWindow.registerCallback(
-      this.kNotificationID,
-      (win) => {
-        if (this.notificationsByWindow.has(win)) {
-          // We've already set up this window.
-          return;
-        }
-
-        this.notificationsByWindow.set(win, new Set());
-
-        // Start listening across all tabs! The UI will
-        // be set up lazily when we actually need to show
-        // a notification.
-        this.delayedInit().then(() => {
-          win.gBrowser.addTabsProgressListener(this);
-        });
-      },
-      (win, closing) => {
-        // If the window is going away, don't bother doing anything.
-        if (closing) {
-          return;
-        }
-
-        let DOMWindowUtils = win.windowUtils;
-        DOMWindowUtils.removeSheetUsingURIString(this.getURL("privileged/FirefoxMonitor.css"),
-                                                 DOMWindowUtils.AUTHOR_SHEET);
-
-        this.notificationsByWindow.get(win).forEach(n => {
-          n.remove();
-        });
-        this.notificationsByWindow.delete(win);
-
-        let doc = win.document;
-        doc.getElementById(`${this.kNotificationID}-notification-anchor`).remove();
-        doc.getElementById(`${this.kNotificationID}-notification`).remove();
-        this.panelUIsByWindow.delete(win);
-
-        win.gBrowser.removeTabsProgressListener(this);
-      },
-    );
-
-    this.observerAdded = true;
-  },
-
-  setupPanelUI(win) {
-    // Inject our stylesheet.
-    let DOMWindowUtils = win.windowUtils;
-    DOMWindowUtils.loadSheetUsingURIString(this.getURL("privileged/FirefoxMonitor.css"),
-                                           DOMWindowUtils.AUTHOR_SHEET);
-
-    // 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);
-    this.panelUIsByWindow.set(win, panelUI);
-    return panelUI;
-  },
-
-  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 = this.panelUIsByWindow.get(win);
-    if (!panelUI) {
-      panelUI = this.setupPanelUI(win);
-    }
-
-    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":
-          this.notificationsByWindow.get(win).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");
-
-    this.notificationsByWindow.get(win).add(n);
-  },
+    FirefoxMonitorContainer.FirefoxMonitor.stopObserving();
+  }
 };
-
-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 = {
-  getString(aKey) {
-    return FirefoxMonitor.getString(aKey);
-  },
-
-  getFormattedString(aKey, args) {
-    return FirefoxMonitor.getFormattedString(aKey, args);
-  },
-
-  get brandString() {
-    if (this._brandString) {
-      return this._brandString;
-    }
-    return this._brandString = this.getString("fxmonitor.brandName");
-  },
-
-  getFirefoxMonitorURL: (aSiteName) => {
-    return `${FirefoxMonitor.FirefoxMonitorURL}/?breach=${encodeURIComponent(aSiteName)}&utm_source=firefox&utm_medium=popup`;
-  },
-
-  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(
-          this.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: () => {
-          FirefoxMonitor.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);
-  },
-};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/fxmonitor/privileged/subscripts/Globals.jsm
@@ -0,0 +1,18 @@
+/* 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");
+ChromeUtils.defineModuleGetter(this, "EveryWindow",
+                               "resource:///modules/EveryWindow.jsm");
+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";