tor-browser

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

fxaPairDevice.js (5044B)


      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 const { XPCOMUtils } = ChromeUtils.importESModule(
      6  "resource://gre/modules/XPCOMUtils.sys.mjs"
      7 );
      8 const { FxAccounts } = ChromeUtils.importESModule(
      9  "resource://gre/modules/FxAccounts.sys.mjs"
     10 );
     11 const { Weave } = ChromeUtils.importESModule(
     12  "resource://services-sync/main.sys.mjs"
     13 );
     14 
     15 ChromeUtils.defineESModuleGetters(this, {
     16  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
     17  FxAccountsPairingFlow: "resource://gre/modules/FxAccountsPairing.sys.mjs",
     18  QR: "moz-src:///toolkit/components/qrcode/encoder.mjs",
     19 });
     20 
     21 // This is only for "labor illusion", see
     22 // https://www.fastcompany.com/3061519/the-ux-secret-that-will-ruin-apps-for-you
     23 const MIN_PAIRING_LOADING_TIME_MS = 1000;
     24 
     25 /**
     26 * Communication between FxAccountsPairingFlow and gFxaPairDeviceDialog
     27 * is done using an emitter via the following messages:
     28 * <- [view:SwitchToWebContent] - Notifies the view to navigate to a specific URL.
     29 * <- [view:Error] - Notifies the view something went wrong during the pairing process.
     30 * -> [view:Closed] - Notifies the pairing module the view was closed.
     31 */
     32 var gFxaPairDeviceDialog = {
     33  init() {
     34    window.addEventListener("unload", () => this.uninit());
     35    document
     36      .getElementById("qrError")
     37      .addEventListener("click", () => this.startPairingFlow());
     38 
     39    this._resetBackgroundQR();
     40    // We let the modal show itself before eventually showing a primary-password dialog later.
     41    Services.tm.dispatchToMainThread(() => this.startPairingFlow());
     42  },
     43 
     44  uninit() {
     45    // When the modal closes we want to remove any query params
     46    // To prevent refreshes/restores from reopening the dialog
     47    const browser = window.docShell.chromeEventHandler;
     48    browser.loadURI(Services.io.newURI("about:preferences#sync"), {
     49      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
     50    });
     51 
     52    this.teardownListeners();
     53    this._emitter.emit("view:Closed");
     54  },
     55 
     56  async startPairingFlow() {
     57    this._resetBackgroundQR();
     58    document
     59      .getElementById("qrWrapper")
     60      .setAttribute("pairing-status", "loading");
     61    this._emitter = new EventEmitter();
     62    this.setupListeners();
     63    try {
     64      if (!Weave.Utils.ensureMPUnlocked()) {
     65        throw new Error("Master-password locked.");
     66      }
     67      // To keep consistent with our accounts.firefox.com counterpart
     68      // we restyle the parent dialog this is contained in
     69      this._styleParentDialog();
     70 
     71      const [, uri] = await Promise.all([
     72        new Promise(res => setTimeout(res, MIN_PAIRING_LOADING_TIME_MS)),
     73        FxAccountsPairingFlow.start({ emitter: this._emitter }),
     74      ]);
     75      const imgData = QR.encodeToDataURI(uri, "L");
     76      document.getElementById("qrContainer").style.backgroundImage =
     77        `url("${imgData.src}")`;
     78      document
     79        .getElementById("qrWrapper")
     80        .setAttribute("pairing-status", "ready");
     81    } catch (e) {
     82      this.onError(e);
     83    }
     84  },
     85 
     86  _styleParentDialog() {
     87    // Since the dialog title is in the above document, we can't query the
     88    // document in this level and need to go up one
     89    let dialogParent = window.parent.document;
     90 
     91    // To allow the firefox icon to go over the dialog
     92    let dialogBox = dialogParent.querySelector(".dialogBox");
     93    dialogBox.style.overflow = "visible";
     94    dialogBox.style.borderRadius = "12px";
     95 
     96    let dialogTitle = dialogParent.querySelector(".dialogTitleBar");
     97    dialogTitle.style.borderBottom = "none";
     98    dialogTitle.classList.add("fxaPairDeviceIcon");
     99  },
    100 
    101  _resetBackgroundQR() {
    102    // The text we encode doesn't really matter as it is un-scannable (blurry and very transparent).
    103    const imgData = QR.encodeToDataURI(
    104      "https://accounts.firefox.com/pair",
    105      "L"
    106    );
    107    document.getElementById("qrContainer").style.backgroundImage =
    108      `url("${imgData.src}")`;
    109  },
    110 
    111  onError(err) {
    112    console.error(err);
    113    this.teardownListeners();
    114    document
    115      .getElementById("qrWrapper")
    116      .setAttribute("pairing-status", "error");
    117  },
    118 
    119  _switchToUrl(url) {
    120    const browser = window.docShell.chromeEventHandler;
    121    browser.fixupAndLoadURIString(url, {
    122      triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
    123        {}
    124      ),
    125    });
    126  },
    127 
    128  setupListeners() {
    129    this._switchToWebContent = (_, url) => this._switchToUrl(url);
    130    this._onError = (_, error) => this.onError(error);
    131    this._emitter.once("view:SwitchToWebContent", this._switchToWebContent);
    132    this._emitter.on("view:Error", this._onError);
    133  },
    134 
    135  teardownListeners() {
    136    try {
    137      this._emitter.off("view:SwitchToWebContent", this._switchToWebContent);
    138      this._emitter.off("view:Error", this._onError);
    139    } catch (e) {
    140      console.warn("Error while tearing down listeners.", e);
    141    }
    142  },
    143 };
    144 
    145 window.addEventListener("load", () => gFxaPairDeviceDialog.init());