Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
firefox / usr / lib / firefox / browser / features / fxmonitor@mozilla.org.xpi
Size: Mime:
PK
!<í.‹s++chrome.manifestlocale fxmonitor en-US en-US/locale/en-US/
PK
!<K-ÂU&&assets/alert.svg<!-- 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>
PK
!<”ã¤@@assets/monitor32.svg<!-- 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" fill="context-fill" fill-opacity="context-fill-opacity" fill-rule="nonzero">
  <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"/>
</svg>
PK
!<îŒ>
background.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
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env webextensions */

"use strict";

browser.fxmonitor.start();
PK
!<nŽ„^ýý'en-US/locale/en-US/fxmonitor.properties
fxmonitor.popupHeader=Have an account on this site?
fxmonitor.brandName=Firefox Monitor
fxmonitor.anchorIcon.tooltiptext=Site reported to %S
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.
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.
fxmonitor.checkButton.label=Check %S
fxmonitor.checkButton.accessKey=C
fxmonitor.dismissButton.label=Dismiss
fxmonitor.dismissButton.accessKey=D
fxmonitor.neverShowButton.label=Never show %S alerts
fxmonitor.neverShowButton.accessKey=N
PK
!<>\ÙØää
manifest.json{
  "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"]]
      }
    }
  }
}
PK
!<S±žÖÖprivileged/FirefoxMonitor.css/* 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;
}
PK
!<”]ËMLLprivileged/api.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
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* globals ExtensionAPI, XPCOMUtils */

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";

this.fxmonitor = class extends ExtensionAPI {
  getAPI(context) {
    return {
      fxmonitor: {
        async start() {
          await 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(),

  // 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",

  // This is here for documentation, will be redefined to a pref getter
  // using XPCOMUtils.defineLazyPreferenceGetter in delayedInit().
  // Telemetry event recording is enabled by default.
  // If this pref exists and is true-y, it's disabled.
  telemetryDisabled: null,
  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);
  },

  // We used to persist the list of hosts we've already warned the
  // user for in this pref. Now, we check the pref at init and
  // if it has a value, migrate the remembered hosts to content prefs
  // and clear this one.
  kWarnedHostsPref: "extensions.fxmonitor.warnedHosts",
  migrateWarnedHostsIfNeeded() {
    if (!Preferences.isSet(this.kWarnedHostsPref)) {
      return;
    }

    let hosts = [];
    try {
      hosts = JSON.parse(Preferences.get(this.kWarnedHostsPref));
    } catch (ex) {
      // Invalid JSON, nothing to be done.
    }

    let loadContext = Cu.createLoadContext();
    for (let host of hosts) {
      this.rememberWarnedHost(loadContext, host);
    }

    Preferences.reset(this.kWarnedHostsPref);
  },

  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;

    XPCOMUtils.defineLazyServiceGetter(
      this,
      "_contentPrefService",
      "@mozilla.org/content-pref/service;1",
      "nsIContentPrefService2"
    );

    this.migrateWarnedHostsIfNeeded();

    // 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,
      },
    });

    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "FirefoxMonitorURL",
      this.kFirefoxMonitorURLPref,
      this.kDefaultFirefoxMonitorURL
    );

    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "firstAlertShown",
      this.kFirstAlertShownPref,
      false
    );

    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "telemetryDisabled",
      this.kTelemetryDisabledPref,
      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) {
      XPCOMUtils.defineLazyGetter(this, "strings", () => {
        Components.manager.addBootstrappedManifestLocation(l10nManifest);

        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
        );

        if (this.notificationsByWindow.has(win)) {
          this.notificationsByWindow.get(win).forEach(n => {
            n.remove();
          });
          this.notificationsByWindow.delete(win);
        }

        if (this.panelUIsByWindow.has(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;
  },

  async hostAlreadyWarned(loadContext, host) {
    return new Promise((resolve, reject) => {
      this._contentPrefService.getByDomainAndName(
        host,
        "extensions.fxmonitor.hostAlreadyWarned",
        loadContext,
        {
          handleCompletion: () => resolve(false),
          handleResult: result => resolve(result.value),
        }
      );
    });
  },

  rememberWarnedHost(loadContext, host) {
    this._contentPrefService.set(
      host,
      "extensions.fxmonitor.hostAlreadyWarned",
      true,
      loadContext
    );
  },

  async warnIfNeeded(browser, host) {
    if (
      !this.enabled ||
      !this.domainMap.has(host) ||
      (await this.hostAlreadyWarned(browser.loadContext, 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.rememberWarnedHost(browser.loadContext, host);

    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
              )
            );
          this.recordEvent("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"),
      }
    );

    this.recordEvent("doorhanger_shown");

    this.notificationsByWindow.get(win).add(n);
  },

  recordEvent(aEventName) {
    if (this.telemetryDisabled) {
      return;
    }

    Services.telemetry.recordEvent("fxmonitor", "interaction", aEventName);
  },
};

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",
          {}
        );

        FirefoxMonitor.recordEvent("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: () => {
          FirefoxMonitor.recordEvent("dismiss_btn");
        },
      },
      {
        label: this.getFormattedString("fxmonitor.neverShowButton.label", [
          this.brandString,
        ]),
        accessKey: this.getString("fxmonitor.neverShowButton.accessKey"),
        callback: () => {
          FirefoxMonitor.disable();
          FirefoxMonitor.recordEvent("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);
  },
};
PK
!<Oµ{‘¸¸privileged/schema.json[
  {
    "namespace": "fxmonitor",
    "functions": [
      {
        "name": "start",
        "type": "function",
        "async": true,
        "parameters": []
      }
    ]
  }
]
PK
!<í.‹s++chrome.manifestPK
!<K-ÂU&&¤Xassets/alert.svgPK
!<”ã¤@@¤¬assets/monitor32.svgPK
!<îŒ>
¤	background.jsPK
!<nŽ„^ýý'a
en-US/locale/en-US/fxmonitor.propertiesPK
!<>\ÙØää
¤£
manifest.jsonPK
!<S±žÖÖ¤²privileged/FirefoxMonitor.cssPK
!<”]ËMLL¤Ãprivileged/api.jsPK
!<Oµ{‘¸¸¤þcprivileged/schema.jsonPK		Vêd