tor-browser

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

BackupUIChild.sys.mjs (6382B)


      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 /**
      6 * A JSWindowActor that is responsible for marshalling information between
      7 * the BackupService singleton and any registered UI widgets that need to
      8 * represent data from that service. Any UI widgets that want to receive
      9 * state updates from BackupService should emit a BackupUI:InitWidget
     10 * event in a document that this actor pair is registered for.
     11 */
     12 export class BackupUIChild extends JSWindowActorChild {
     13  #inittedWidgets = new WeakSet();
     14 
     15  /**
     16   * Handles custom events fired by widgets that want to register with
     17   * BackupUIChild.
     18   *
     19   * @param {Event} event
     20   *   The custom event that the widget fired.
     21   */
     22  async handleEvent(event) {
     23    /**
     24     * BackupUI:InitWidget sends a message to the parent to request the BackupService state
     25     * which will result in a `backupServiceState` property of the widget to be set when that
     26     * state is received. Subsequent state updates will also cause that state property to
     27     * be set.
     28     */
     29    if (event.type == "BackupUI:InitWidget") {
     30      this.#inittedWidgets.add(event.target);
     31      this.sendAsyncMessage("RequestState");
     32    } else if (event.type == "BackupUI:TriggerCreateBackup") {
     33      let result = await this.sendQuery("TriggerCreateBackup", event.detail);
     34 
     35      if (!result.success) {
     36        event.target.backupErrorCode = result.errorCode;
     37      }
     38    } else if (event.type == "BackupUI:EnableScheduledBackups") {
     39      const target = event.target;
     40 
     41      const result = await this.sendQuery(
     42        "EnableScheduledBackups",
     43        event.detail
     44      );
     45      if (result.success) {
     46        target.close();
     47      } else {
     48        target.enableBackupErrorCode = result.errorCode;
     49      }
     50    } else if (event.type == "BackupUI:DisableScheduledBackups") {
     51      const target = event.target;
     52 
     53      this.sendAsyncMessage("DisableScheduledBackups", event.detail);
     54      // backups will always end up disabled even if there was an error
     55      // with other bookkeeping related to turning off backups
     56 
     57      target.close();
     58    } else if (event.type == "BackupUI:ShowFilepicker") {
     59      let targetNodeName = event.composedTarget.nodeName;
     60      let { path, filename, iconURL } = await this.sendQuery("ShowFilepicker", {
     61        win: event.detail?.win,
     62        filter: event.detail?.filter,
     63        existingBackupPath: event.detail?.existingBackupPath,
     64      });
     65 
     66      let widgets = ChromeUtils.nondeterministicGetWeakSetKeys(
     67        this.#inittedWidgets
     68      );
     69 
     70      for (let widget of widgets) {
     71        if (widget.isConnected && widget.nodeName == targetNodeName) {
     72          const win = widget.ownerGlobal;
     73          // Using Cu.cloneInto here allows us to embed components that use this event
     74          // in non-parent-processes such as about:welcome
     75          const detail = Cu.cloneInto({ path, filename, iconURL }, win, {
     76            wrapReflectors: true,
     77          });
     78          const event = new win.CustomEvent(
     79            "BackupUI:SelectNewFilepickerPath",
     80            {
     81              bubbles: true,
     82              composed: true,
     83              detail,
     84            }
     85          );
     86          widget.dispatchEvent(event);
     87          break;
     88        }
     89      }
     90    } else if (event.type == "BackupUI:GetBackupFileInfo") {
     91      let { backupFile } = event.detail;
     92      this.sendAsyncMessage("GetBackupFileInfo", {
     93        backupFile,
     94      });
     95    } else if (event.type == "BackupUI:RestoreFromBackupFile") {
     96      let { backupFile, backupPassword } = event.detail;
     97      let result = await this.sendQuery("RestoreFromBackupFile", {
     98        backupFile,
     99        backupPassword,
    100      });
    101 
    102      if (result.success) {
    103        event.target.restoreFromBackupDialogEl?.close();
    104 
    105        // Since we always launch the new profile from this event, let's close the current instance now
    106        this.sendAsyncMessage("QuitCurrentProfile");
    107      }
    108    } else if (event.type == "BackupUI:RestoreFromBackupChooseFile") {
    109      this.sendAsyncMessage("RestoreFromBackupChooseFile");
    110    } else if (event.type == "BackupUI:EnableEncryption") {
    111      const target = event.target;
    112 
    113      const result = await this.sendQuery("EnableEncryption", event.detail);
    114      if (result.success) {
    115        target.close();
    116      } else {
    117        target.enableEncryptionErrorCode = result.errorCode;
    118      }
    119    } else if (event.type == "BackupUI:DisableEncryption") {
    120      const target = event.target;
    121 
    122      const result = await this.sendQuery("DisableEncryption", event.detail);
    123      if (result.success) {
    124        target.close();
    125      } else {
    126        target.disableEncryptionErrorCode = result.errorCode;
    127      }
    128    } else if (event.type == "BackupUI:ShowBackupLocation") {
    129      this.sendAsyncMessage("ShowBackupLocation");
    130    } else if (event.type == "BackupUI:EditBackupLocation") {
    131      this.sendAsyncMessage("EditBackupLocation");
    132    } else if (event.type == "BackupUI:SetEmbeddedComponentPersistentData") {
    133      this.sendAsyncMessage("SetEmbeddedComponentPersistentData", event.detail);
    134    } else if (event.type == "BackupUI:FlushEmbeddedComponentPersistentData") {
    135      this.sendAsyncMessage("FlushEmbeddedComponentPersistentData");
    136    } else if (event.type == "BackupUI:ErrorBarDismissed") {
    137      this.sendAsyncMessage("ErrorBarDismissed");
    138    }
    139  }
    140 
    141  /**
    142   * Handles messages sent by BackupUIParent.
    143   *
    144   * @param {ReceiveMessageArgument} message
    145   *   The message received from the BackupUIParent.
    146   */
    147  receiveMessage(message) {
    148    if (message.name == "StateUpdate") {
    149      let widgets = ChromeUtils.nondeterministicGetWeakSetKeys(
    150        this.#inittedWidgets
    151      );
    152      for (let widget of widgets) {
    153        if (!widget.isConnected || !widget.ownerGlobal) {
    154          continue;
    155        }
    156 
    157        const state = Cu.cloneInto(message.data.state, widget.ownerGlobal);
    158 
    159        const waivedWidget = Cu.waiveXrays(widget);
    160        waivedWidget.backupServiceState = state;
    161        //dispatch the event for the React listeners
    162        widget.dispatchEvent(
    163          new this.contentWindow.CustomEvent("BackupUI:StateWasUpdated", {
    164            bubbles: true,
    165            composed: true,
    166            detail: { state },
    167          })
    168        );
    169      }
    170    }
    171  }
    172 }