tor-browser

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

BackupUIParent.sys.mjs (10751B)


      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 lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  BackupService: "resource:///modules/backup/BackupService.sys.mjs",
      9  ERRORS: "chrome://browser/content/backup/backup-constants.mjs",
     10  E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
     11 });
     12 
     13 ChromeUtils.defineLazyGetter(lazy, "logConsole", function () {
     14  return console.createInstance({
     15    prefix: "BackupUIParent",
     16    maxLogLevel: Services.prefs.getBoolPref("browser.backup.log", false)
     17      ? "Debug"
     18      : "Warn",
     19  });
     20 });
     21 
     22 const BACKUP_ERROR_CODE_PREF_NAME = "browser.backup.errorCode";
     23 
     24 /**
     25 * A JSWindowActor that is responsible for marshalling information between
     26 * the BackupService singleton and any registered UI widgets that need to
     27 * represent data from that service.
     28 */
     29 export class BackupUIParent extends JSWindowActorParent {
     30  /**
     31   * A reference to the BackupService singleton instance.
     32   *
     33   * @type {BackupService}
     34   */
     35  #bs;
     36 
     37  /**
     38   * Observer for "backup-service-status-updated" notifications.
     39   * We want each BackupUIParent actor instance to be notified separately and
     40   * to forward the state to its child.
     41   */
     42  #obs;
     43 
     44  /**
     45   * Create a BackupUIParent instance. If a BackupUIParent is instantiated
     46   * before BrowserGlue has a chance to initialize the BackupService, this
     47   * constructor will cause it to initialize first.
     48   */
     49  constructor() {
     50    super();
     51    // We use init() rather than get(), since it's possible to load
     52    // about:preferences before the service has had a chance to init itself
     53    // via BrowserGlue.
     54    this.#bs = lazy.BackupService.init();
     55 
     56    // Define the observer function to capture our this.
     57    this.#obs = (_subject, topic) => {
     58      if (topic == "backup-service-status-updated") {
     59        this.sendState();
     60      }
     61    };
     62  }
     63 
     64  /**
     65   * Called once the BackupUIParent/BackupUIChild pair have been connected.
     66   */
     67  actorCreated() {
     68    this.#bs.addEventListener("BackupService:StateUpdate", this);
     69    Services.obs.addObserver(this.#obs, "backup-service-status-updated");
     70    // Note that loadEncryptionState is an async function.
     71    // This function is no-op if the encryption state was already loaded.
     72    this.#bs.loadEncryptionState();
     73  }
     74 
     75  /**
     76   * Called once the BackupUIParent/BackupUIChild pair have been disconnected.
     77   */
     78  didDestroy() {
     79    this.#bs.removeEventListener("BackupService:StateUpdate", this);
     80    Services.obs.removeObserver(this.#obs, "backup-service-status-updated");
     81  }
     82 
     83  /**
     84   * Handles events fired by the BackupService.
     85   *
     86   * @param {Event} event
     87   *   The event that the BackupService emitted.
     88   */
     89  handleEvent(event) {
     90    if (event.type == "BackupService:StateUpdate") {
     91      this.sendState();
     92    }
     93  }
     94 
     95  /**
     96   * Trigger a createBackup call.
     97   *
     98   * @param {...any} args
     99   *   Arguments to pass through to createBackup.
    100   * @returns {object} Result of the backup attempt.
    101   */
    102  async #triggerCreateBackup(...args) {
    103    try {
    104      await this.#bs.createBackup(...args);
    105      return { success: true };
    106    } catch (e) {
    107      lazy.logConsole.error(`Failed to retrigger backup`, e);
    108      return { success: false, errorCode: e.cause || lazy.ERRORS.UNKNOWN };
    109    }
    110  }
    111 
    112  /**
    113   * Handles messages sent by BackupUIChild.
    114   *
    115   * @param {ReceiveMessageArgument} message
    116   *   The message received from the BackupUIChild.
    117   * @returns {
    118   *   null |
    119   *   {success: boolean, errorCode: number} |
    120   *   {path: string, fileName: string, iconURL: string|null}
    121   * }
    122   *   Returns either a success object, a file details object, or null.
    123   */
    124  async receiveMessage(message) {
    125    let currentWindowGlobal = this.browsingContext.currentWindowGlobal;
    126    // The backup spotlights can be embedded in less privileged content pages, so let's
    127    // make sure that any messages from content are coming from the privileged
    128    // about content process type
    129    if (
    130      !currentWindowGlobal ||
    131      (!currentWindowGlobal.isInProcess &&
    132        this.browsingContext.currentRemoteType !=
    133          lazy.E10SUtils.PRIVILEGEDABOUT_REMOTE_TYPE)
    134    ) {
    135      lazy.logConsole.debug(
    136        "BackupUIParent: received message from the wrong content process type."
    137      );
    138      return null;
    139    }
    140 
    141    if (message.name == "RequestState") {
    142      this.sendState();
    143    } else if (message.name == "TriggerCreateBackup") {
    144      return await this.#triggerCreateBackup({ reason: "manual" });
    145    } else if (message.name == "EnableScheduledBackups") {
    146      try {
    147        let { parentDirPath, password } = message.data;
    148        if (parentDirPath) {
    149          this.#bs.setParentDirPath(parentDirPath);
    150        }
    151 
    152        if (password) {
    153          // If the user's previously created backups were already encrypted
    154          // with a password, their encryption settings are now reset to
    155          // accommodate the newly supplied password.
    156          if (await this.#bs.loadEncryptionState()) {
    157            await this.#bs.disableEncryption();
    158          }
    159          await this.#bs.enableEncryption(password);
    160          Glean.browserBackup.passwordAdded.record();
    161        }
    162        this.#bs.setScheduledBackups(true);
    163      } catch (e) {
    164        lazy.logConsole.error(`Failed to enable scheduled backups`, e);
    165        return { success: false, errorCode: e.cause || lazy.ERRORS.UNKNOWN };
    166      }
    167      /**
    168       * TODO: (Bug 1900125) we should create a backup at the specified dir path once we turn on
    169       * scheduled backups. The backup folder in the chosen directory should contain
    170       * the archive file, which we create using BackupService.createArchive implemented in
    171       * Bug 1897498.
    172       */
    173      return { success: true };
    174    } else if (message.name == "DisableScheduledBackups") {
    175      await this.#bs.cleanupBackupFiles();
    176      this.#bs.setScheduledBackups(false);
    177    } else if (message.name == "ShowFilepicker") {
    178      let { win, filter, existingBackupPath } = message.data;
    179 
    180      let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
    181 
    182      let mode = filter
    183        ? Ci.nsIFilePicker.modeOpen
    184        : Ci.nsIFilePicker.modeGetFolder;
    185      fp.init(win || this.browsingContext, "", mode);
    186 
    187      if (filter) {
    188        fp.appendFilters(Ci.nsIFilePicker[filter]);
    189      }
    190 
    191      if (existingBackupPath) {
    192        try {
    193          let folder = (await IOUtils.getFile(existingBackupPath)).parent;
    194          if (folder.exists()) {
    195            fp.displayDirectory = folder;
    196          }
    197        } catch (_) {
    198          // If the file can not be found we will skip setting the displayDirectory.
    199        }
    200      }
    201 
    202      let result = await new Promise(resolve => fp.open(resolve));
    203 
    204      if (result === Ci.nsIFilePicker.returnCancel) {
    205        return null;
    206      }
    207 
    208      let path = fp.file.path;
    209      let iconURL = this.#bs.getIconFromFilePath(path);
    210      let filename = PathUtils.filename(path);
    211 
    212      return {
    213        path,
    214        filename,
    215        iconURL,
    216      };
    217    } else if (message.name == "GetBackupFileInfo") {
    218      let { backupFile } = message.data;
    219      try {
    220        await this.#bs.getBackupFileInfo(backupFile);
    221      } catch (e) {
    222        /**
    223         * TODO: (Bug 1905156) display a localized version of error in the restore dialog.
    224         */
    225      }
    226    } else if (message.name == "RestoreFromBackupChooseFile") {
    227      const window = this.browsingContext.topChromeWindow;
    228      this.#bs.filePickerForRestore(window);
    229    } else if (message.name == "RestoreFromBackupFile") {
    230      let { backupFile, backupPassword } = message.data;
    231      try {
    232        await this.#bs.recoverFromBackupArchive(
    233          backupFile,
    234          backupPassword,
    235          true /* shouldLaunch */
    236        );
    237      } catch (e) {
    238        lazy.logConsole.error(`Failed to restore file: ${backupFile}`, e);
    239        this.#bs.setRecoveryError(e.cause || lazy.ERRORS.UNKNOWN);
    240        return { success: false, errorCode: e.cause || lazy.ERRORS.UNKNOWN };
    241      }
    242      return { success: true };
    243    } else if (message.name == "EnableEncryption") {
    244      try {
    245        let wasEncrypted = this.#bs.state.encryptionEnabled;
    246        await this.#bs.enableEncryption(message.data.password);
    247        if (wasEncrypted) {
    248          Glean.browserBackup.passwordChanged.record();
    249        } else {
    250          Glean.browserBackup.passwordAdded.record();
    251        }
    252      } catch (e) {
    253        lazy.logConsole.error(`Failed to enable encryption`, e);
    254        return { success: false, errorCode: e.cause || lazy.ERRORS.UNKNOWN };
    255      }
    256 
    257      return await this.#triggerCreateBackup({ reason: "encryption" });
    258    } else if (message.name == "DisableEncryption") {
    259      try {
    260        await this.#bs.disableEncryption();
    261        Glean.browserBackup.passwordRemoved.record();
    262      } catch (e) {
    263        lazy.logConsole.error(`Failed to disable encryption`, e);
    264        return { success: false, errorCode: e.cause || lazy.ERRORS.UNKNOWN };
    265      }
    266 
    267      return await this.#triggerCreateBackup({ reason: "encryption" });
    268    } else if (message.name == "ShowBackupLocation") {
    269      this.#bs.showBackupLocation();
    270    } else if (message.name == "EditBackupLocation") {
    271      const window = this.browsingContext.topChromeWindow;
    272      this.#bs.editBackupLocation(window);
    273    } else if (message.name == "QuitCurrentProfile") {
    274      // Notify windows that a quit has been requested.
    275      let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
    276        Ci.nsISupportsPRBool
    277      );
    278      Services.obs.notifyObservers(cancelQuit, "quit-application-requested");
    279      if (cancelQuit.data) {
    280        // Something blocked our attempt to quit.
    281        return null;
    282      }
    283 
    284      try {
    285        Services.startup.quit(Services.startup.eAttemptQuit);
    286      } catch (e) {
    287        // let's silently resolve this error
    288        lazy.logConsole.error(
    289          `There was a problem while quitting the current profile: `,
    290          e
    291        );
    292      }
    293    } else if (message.name == "SetEmbeddedComponentPersistentData") {
    294      this.#bs.setEmbeddedComponentPersistentData(message.data);
    295    } else if (message.name == "FlushEmbeddedComponentPersistentData") {
    296      this.#bs.setEmbeddedComponentPersistentData({});
    297    } else if (message.name == "ErrorBarDismissed") {
    298      Services.prefs.setIntPref(BACKUP_ERROR_CODE_PREF_NAME, lazy.ERRORS.NONE);
    299    }
    300 
    301    return null;
    302  }
    303 
    304  /**
    305   * Sends the StateUpdate message to the BackupUIChild, along with the most
    306   * recent state object from BackupService.
    307   */
    308  sendState() {
    309    this.sendAsyncMessage("StateUpdate", {
    310      state: this.#bs.state,
    311    });
    312  }
    313 }