tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AboutLoginsChild.sys.mjs (9077B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import { LoginHelper } from "resource://gre/modules/LoginHelper.sys.mjs";
      6 
      7 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
      8 
      9 const lazy = {};
     10 
     11 XPCOMUtils.defineLazyServiceGetter(
     12  lazy,
     13  "ClipboardHelper",
     14  "@mozilla.org/widget/clipboardhelper;1",
     15  Ci.nsIClipboardHelper
     16 );
     17 
     18 const TELEMETRY_MIN_MS_BETWEEN_OPEN_MANAGEMENT = 5000;
     19 
     20 let gLastOpenManagementBrowserId = null;
     21 let gLastOpenManagementEventTime = Number.NEGATIVE_INFINITY;
     22 let gPrimaryPasswordPromise;
     23 
     24 function recordTelemetryEvent(event) {
     25  try {
     26    let { name, extra = {}, value = null } = event;
     27    if (value) {
     28      extra.value = value;
     29    }
     30    Glean.pwmgr[name].record(extra);
     31  } catch (ex) {
     32    console.error("AboutLoginsChild: error recording telemetry event:", ex);
     33  }
     34 }
     35 
     36 export class AboutLoginsChild extends JSWindowActorChild {
     37  handleEvent(event) {
     38    switch (event.type) {
     39      case "AboutLoginsInit": {
     40        this.#aboutLoginsInit();
     41        break;
     42      }
     43      case "AboutLoginsImportReportInit": {
     44        this.#aboutLoginsImportReportInit();
     45        break;
     46      }
     47      case "AboutLoginsCopyLoginDetail": {
     48        this.#aboutLoginsCopyLoginDetail(event.detail);
     49        break;
     50      }
     51      case "AboutLoginsCreateLogin": {
     52        this.#aboutLoginsCreateLogin(event.detail);
     53        break;
     54      }
     55      case "AboutLoginsDeleteLogin": {
     56        this.#aboutLoginsDeleteLogin(event.detail);
     57        break;
     58      }
     59      case "AboutLoginsExportPasswords": {
     60        this.#aboutLoginsExportPasswords();
     61        break;
     62      }
     63      case "AboutLoginsGetHelp": {
     64        this.#aboutLoginsGetHelp();
     65        break;
     66      }
     67      case "AboutLoginsImportFromBrowser": {
     68        this.#aboutLoginsImportFromBrowser();
     69        break;
     70      }
     71      case "AboutLoginsImportFromFile": {
     72        this.#aboutLoginsImportFromFile();
     73        break;
     74      }
     75      case "AboutLoginsOpenPreferences": {
     76        this.#aboutLoginsOpenPreferences();
     77        break;
     78      }
     79      case "AboutLoginsRecordTelemetryEvent": {
     80        this.#aboutLoginsRecordTelemetryEvent(event);
     81        break;
     82      }
     83      case "AboutLoginsRemoveAllLogins": {
     84        this.#aboutLoginsRemoveAllLogins();
     85        break;
     86      }
     87      case "AboutLoginsSortChanged": {
     88        this.#aboutLoginsSortChanged(event.detail);
     89        break;
     90      }
     91      case "AboutLoginsSyncEnable": {
     92        this.#aboutLoginsSyncEnable();
     93        break;
     94      }
     95      case "AboutLoginsUpdateLogin": {
     96        this.#aboutLoginsUpdateLogin(event.detail);
     97        break;
     98      }
     99    }
    100  }
    101 
    102  #aboutLoginsInit() {
    103    this.sendAsyncMessage("AboutLogins:Subscribe");
    104 
    105    let win = this.browsingContext.window;
    106    let waivedContent = Cu.waiveXrays(win);
    107    let that = this;
    108    let AboutLoginsUtils = {
    109      doLoginsMatch(loginA, loginB) {
    110        return LoginHelper.doLoginsMatch(loginA, loginB, {});
    111      },
    112      getLoginOrigin(uriString) {
    113        return LoginHelper.getLoginOrigin(uriString);
    114      },
    115      setFocus(element) {
    116        Services.focus.setFocus(element, Services.focus.FLAG_BYKEY);
    117      },
    118      /**
    119       * Shows the Primary Password prompt if enabled, or the
    120       * OS auth dialog otherwise.
    121       *
    122       * @param resolve Callback that is called with result of authentication.
    123       * @param messageId The string ID that corresponds to a string stored in aboutLogins.ftl.
    124       *                  This string will be displayed only when the OS auth dialog is used.
    125       * @param reason The reason for requesting reauthentication, used for telemetry.
    126       */
    127      async promptForPrimaryPassword(resolve, messageId, reason) {
    128        gPrimaryPasswordPromise = {
    129          resolve,
    130        };
    131 
    132        that.sendAsyncMessage("AboutLogins:PrimaryPasswordRequest", {
    133          messageId,
    134          reason,
    135        });
    136 
    137        return gPrimaryPasswordPromise;
    138      },
    139      // Default to enabled just in case a search is attempted before we get a response.
    140      primaryPasswordEnabled: true,
    141      passwordRevealVisible: true,
    142    };
    143    waivedContent.AboutLoginsUtils = Cu.cloneInto(
    144      AboutLoginsUtils,
    145      waivedContent,
    146      {
    147        cloneFunctions: true,
    148      }
    149    );
    150  }
    151 
    152  #aboutLoginsImportReportInit() {
    153    this.sendAsyncMessage("AboutLogins:ImportReportInit");
    154  }
    155 
    156  #aboutLoginsCopyLoginDetail(detail) {
    157    lazy.ClipboardHelper.copyString(
    158      detail,
    159      this.windowContext,
    160      lazy.ClipboardHelper.Sensitive
    161    );
    162  }
    163 
    164  #aboutLoginsCreateLogin(login) {
    165    this.sendAsyncMessage("AboutLogins:CreateLogin", {
    166      login,
    167    });
    168  }
    169 
    170  #aboutLoginsDeleteLogin(login) {
    171    this.sendAsyncMessage("AboutLogins:DeleteLogin", {
    172      login,
    173    });
    174  }
    175 
    176  #aboutLoginsExportPasswords() {
    177    this.sendAsyncMessage("AboutLogins:ExportPasswords");
    178  }
    179 
    180  #aboutLoginsGetHelp() {
    181    this.sendAsyncMessage("AboutLogins:GetHelp");
    182  }
    183 
    184  #aboutLoginsImportFromBrowser() {
    185    this.sendAsyncMessage("AboutLogins:ImportFromBrowser");
    186    recordTelemetryEvent({
    187      name: "mgmtMenuItemUsedImportFromBrowser",
    188    });
    189  }
    190 
    191  #aboutLoginsImportFromFile() {
    192    this.sendAsyncMessage("AboutLogins:ImportFromFile");
    193    recordTelemetryEvent({
    194      name: "mgmtMenuItemUsedImportFromCsv",
    195    });
    196  }
    197 
    198  #aboutLoginsOpenPreferences() {
    199    this.sendAsyncMessage("AboutLogins:OpenPreferences");
    200    recordTelemetryEvent({
    201      name: "mgmtMenuItemUsedPreferences",
    202    });
    203  }
    204 
    205  #aboutLoginsRecordTelemetryEvent(event) {
    206    if (event.detail.name.startsWith("openManagement")) {
    207      let { docShell } = this.browsingContext;
    208      // Compare to the last time open_management was recorded for the same
    209      // outerWindowID to not double-count them due to a redirect to remove
    210      // the entryPoint query param (since replaceState isn't allowed for
    211      // about:). Don't use performance.now for the tab since you can't
    212      // compare that number between different tabs and this JSM is shared.
    213      let now = docShell.now();
    214      if (
    215        this.browsingContext.browserId == gLastOpenManagementBrowserId &&
    216        now - gLastOpenManagementEventTime <
    217          TELEMETRY_MIN_MS_BETWEEN_OPEN_MANAGEMENT
    218      ) {
    219        return;
    220      }
    221      gLastOpenManagementEventTime = now;
    222      gLastOpenManagementBrowserId = this.browsingContext.browserId;
    223    }
    224    recordTelemetryEvent(event.detail);
    225  }
    226 
    227  #aboutLoginsRemoveAllLogins() {
    228    this.sendAsyncMessage("AboutLogins:RemoveAllLogins");
    229  }
    230 
    231  #aboutLoginsSortChanged(detail) {
    232    this.sendAsyncMessage("AboutLogins:SortChanged", detail);
    233  }
    234 
    235  #aboutLoginsSyncEnable() {
    236    this.sendAsyncMessage("AboutLogins:SyncEnable");
    237  }
    238 
    239  #aboutLoginsUpdateLogin(login) {
    240    this.sendAsyncMessage("AboutLogins:UpdateLogin", {
    241      login,
    242    });
    243  }
    244 
    245  // eslint-disable-next-line consistent-return
    246  receiveMessage(message) {
    247    switch (message.name) {
    248      case "AboutLogins:ImportReportData":
    249        this.#importReportData(message.data);
    250        break;
    251      case "AboutLogins:PrimaryPasswordResponse":
    252        this.#primaryPasswordResponse(message.data);
    253        break;
    254      case "AboutLogins:RemaskPassword":
    255        this.#remaskPassword(message.data);
    256        break;
    257      case "AboutLogins:Setup":
    258        this.#setup(message.data);
    259        break;
    260      case "AboutLogins:WaitForFocus": {
    261        return new Promise(resolve => {
    262          if (!this.document.hasFocus()) {
    263            this.document.ownerGlobal.addEventListener(
    264              "focus",
    265              () => {
    266                resolve();
    267              },
    268              { once: true }
    269            );
    270          } else {
    271            resolve();
    272          }
    273        });
    274      }
    275      default:
    276        this.#passMessageDataToContent(message);
    277    }
    278  }
    279 
    280  #importReportData(data) {
    281    this.sendToContent("ImportReportData", data);
    282  }
    283 
    284  #primaryPasswordResponse(data) {
    285    if (gPrimaryPasswordPromise) {
    286      gPrimaryPasswordPromise.resolve(data.result);
    287      recordTelemetryEvent(data.telemetryEvent);
    288    }
    289  }
    290 
    291  #remaskPassword(data) {
    292    this.sendToContent("RemaskPassword", data);
    293  }
    294 
    295  #setup(data) {
    296    let utils = Cu.waiveXrays(this.browsingContext.window).AboutLoginsUtils;
    297    utils.primaryPasswordEnabled = data.primaryPasswordEnabled;
    298    utils.passwordRevealVisible = data.passwordRevealVisible;
    299    utils.importVisible = data.importVisible;
    300    utils.supportBaseURL = Services.urlFormatter.formatURLPref(
    301      "app.support.baseURL"
    302    );
    303    this.sendToContent("Setup", data);
    304  }
    305 
    306  #passMessageDataToContent(message) {
    307    this.sendToContent(message.name.replace("AboutLogins:", ""), message.data);
    308  }
    309 
    310  sendToContent(messageType, detail) {
    311    let win = this.document.defaultView;
    312    let message = Object.assign({ messageType }, { value: detail });
    313    let event = new win.CustomEvent("AboutLoginsChromeToContent", {
    314      detail: Cu.cloneInto(message, win),
    315    });
    316    win.dispatchEvent(event);
    317  }
    318 }