tor-browser

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

IndexedDBHelper.sys.mjs (6843B)


      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 file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 var DEBUG = 0;
      6 var debug;
      7 if (DEBUG) {
      8  debug = function (s) {
      9    dump("-*- IndexedDBHelper: " + s + "\n");
     10  };
     11 } else {
     12  debug = function () {};
     13 }
     14 
     15 function getErrorName(err) {
     16  return (err && err.name) || "UnknownError";
     17 }
     18 
     19 export function IndexedDBHelper() {}
     20 
     21 IndexedDBHelper.prototype = {
     22  // Close the database
     23  close: function close() {
     24    if (this._db) {
     25      this._db.close();
     26      this._db = null;
     27    }
     28  },
     29 
     30  /**
     31   * Open a new database.
     32   * User has to provide upgradeSchema.
     33   *
     34   * @param successCb
     35   *        Success callback to call once database is open.
     36   * @param failureCb
     37   *        Error callback to call when an error is encountered.
     38   */
     39  open: function open(aCallback) {
     40    if (aCallback && !this._waitForOpenCallbacks.has(aCallback)) {
     41      this._waitForOpenCallbacks.add(aCallback);
     42      if (this._waitForOpenCallbacks.size !== 1) {
     43        return;
     44      }
     45    }
     46 
     47    let self = this;
     48    let invokeCallbacks = err => {
     49      for (let callback of self._waitForOpenCallbacks) {
     50        callback(err);
     51      }
     52      self._waitForOpenCallbacks.clear();
     53    };
     54 
     55    if (DEBUG) {
     56      debug("Try to open database:" + self.dbName + " " + self.dbVersion);
     57    }
     58    let req;
     59    try {
     60      req = indexedDB.open(this.dbName, this.dbVersion);
     61    } catch (e) {
     62      if (DEBUG) {
     63        debug("Error opening database: " + self.dbName);
     64      }
     65      Services.tm.dispatchToMainThread(() => invokeCallbacks(getErrorName(e)));
     66      return;
     67    }
     68    req.onsuccess = function (event) {
     69      if (DEBUG) {
     70        debug("Opened database:" + self.dbName + " " + self.dbVersion);
     71      }
     72      self._db = event.target.result;
     73      self._db.onversionchange = function () {
     74        if (DEBUG) {
     75          debug("WARNING: DB modified from a different window.");
     76        }
     77      };
     78      invokeCallbacks();
     79    };
     80 
     81    req.onupgradeneeded = function (aEvent) {
     82      if (DEBUG) {
     83        debug(
     84          "Database needs upgrade:" +
     85            self.dbName +
     86            aEvent.oldVersion +
     87            aEvent.newVersion
     88        );
     89        debug(
     90          "Correct new database version:" +
     91            (aEvent.newVersion == this.dbVersion)
     92        );
     93      }
     94 
     95      let _db = aEvent.target.result;
     96      self.upgradeSchema(
     97        req.transaction,
     98        _db,
     99        aEvent.oldVersion,
    100        aEvent.newVersion
    101      );
    102    };
    103    req.onerror = function (aEvent) {
    104      if (DEBUG) {
    105        debug("Failed to open database: " + self.dbName);
    106      }
    107      invokeCallbacks(getErrorName(aEvent.target.error));
    108    };
    109    req.onblocked = function () {
    110      if (DEBUG) {
    111        debug("Opening database request is blocked.");
    112      }
    113    };
    114  },
    115 
    116  /**
    117   * Use the cached DB or open a new one.
    118   *
    119   * @param successCb
    120   *        Success callback to call.
    121   * @param failureCb
    122   *        Error callback to call when an error is encountered.
    123   */
    124  ensureDB: function ensureDB(aSuccessCb, aFailureCb) {
    125    if (this._db) {
    126      if (DEBUG) {
    127        debug("ensureDB: already have a database, returning early.");
    128      }
    129      if (aSuccessCb) {
    130        Services.tm.dispatchToMainThread(aSuccessCb);
    131      }
    132      return;
    133    }
    134    this.open(aError => {
    135      if (aError) {
    136        aFailureCb && aFailureCb(aError);
    137      } else {
    138        aSuccessCb && aSuccessCb();
    139      }
    140    });
    141  },
    142 
    143  /**
    144   * Start a new transaction.
    145   *
    146   * @param txn_type
    147   *        Type of transaction (e.g. "readwrite")
    148   * @param store_name
    149   *        The object store you want to be passed to the callback
    150   * @param callback
    151   *        Function to call when the transaction is available. It will
    152   *        be invoked with the transaction and the `store' object store.
    153   * @param successCb
    154   *        Success callback to call on a successful transaction commit.
    155   *        The result is stored in txn.result (in the callback function).
    156   * @param failureCb
    157   *        Error callback to call when an error is encountered.
    158   */
    159  newTxn: function newTxn(
    160    txn_type,
    161    store_name,
    162    callback,
    163    successCb,
    164    failureCb
    165  ) {
    166    this.ensureDB(() => {
    167      if (DEBUG) {
    168        debug("Starting new transaction" + txn_type);
    169      }
    170      let txn;
    171      try {
    172        txn = this._db.transaction(
    173          Array.isArray(store_name) ? store_name : this.dbStoreNames,
    174          txn_type
    175        );
    176      } catch (e) {
    177        if (DEBUG) {
    178          debug("Error starting transaction: " + this.dbName);
    179        }
    180        failureCb(getErrorName(e));
    181        return;
    182      }
    183      if (DEBUG) {
    184        debug("Retrieving object store: " + this.dbName);
    185      }
    186      let stores;
    187      if (Array.isArray(store_name)) {
    188        stores = [];
    189        for (let i = 0; i < store_name.length; ++i) {
    190          stores.push(txn.objectStore(store_name[i]));
    191        }
    192      } else {
    193        stores = txn.objectStore(store_name);
    194      }
    195 
    196      txn.oncomplete = function () {
    197        if (DEBUG) {
    198          debug("Transaction complete. Returning to callback.");
    199        }
    200        /*
    201         * txn.result property is not part of the transaction object returned
    202         * by this._db.transaction method called above.
    203         * The property is expected to be set in the callback function.
    204         * However, it can happen that the property is not set for some reason,
    205         * so we have to check if the property exists before calling the
    206         * success callback.
    207         */
    208        if (successCb) {
    209          if ("result" in txn) {
    210            successCb(txn.result);
    211          } else {
    212            successCb();
    213          }
    214        }
    215      };
    216 
    217      txn.onabort = function () {
    218        if (DEBUG) {
    219          debug("Caught error on transaction");
    220        }
    221        /*
    222         * txn.error property is part of the transaction object returned by
    223         * this._db.transaction method called above.
    224         * The attribute is defined in IDBTranscation WebIDL interface.
    225         * It may be null.
    226         */
    227        if (failureCb) {
    228          failureCb(getErrorName(txn.error));
    229        }
    230      };
    231      callback(txn, stores);
    232    }, failureCb);
    233  },
    234 
    235  /**
    236   * Initialize the DB. Does not call open.
    237   *
    238   * @param aDBName
    239   *        DB name for the open call.
    240   * @param aDBVersion
    241   *        Current DB version. User has to implement upgradeSchema.
    242   * @param aDBStoreName
    243   *        ObjectStore that is used.
    244   */
    245  initDBHelper: function initDBHelper(aDBName, aDBVersion, aDBStoreNames) {
    246    this.dbName = aDBName;
    247    this.dbVersion = aDBVersion;
    248    this.dbStoreNames = aDBStoreNames;
    249    // Cache the database.
    250    this._db = null;
    251    this._waitForOpenCallbacks = new Set();
    252  },
    253 };