tor-browser

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

DelayedInit.sys.mjs (5480B)


      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 * Use DelayedInit to schedule initializers to run some time after startup.
      7 * Initializers are added to a list of pending inits. Whenever the main thread
      8 * message loop is idle, DelayedInit will start running initializers from the
      9 * pending list. To prevent monopolizing the message loop, every idling period
     10 * has a maximum duration. When that's reached, we give up the message loop and
     11 * wait for the next idle.
     12 *
     13 * DelayedInit is compatible with lazy getters like those from XPCOMUtils. When
     14 * the lazy getter is first accessed, its corresponding initializer is run
     15 * automatically if it hasn't been run already. Each initializer also has a
     16 * maximum wait parameter that specifies a mandatory timeout; when the timeout
     17 * is reached, the initializer is forced to run.
     18 *
     19 *   DelayedInit.schedule(() => Foo.init(), null, null, 5000);
     20 *
     21 * In the example above, Foo.init will run automatically when the message loop
     22 * becomes idle, or when 5000ms has elapsed, whichever comes first.
     23 *
     24 *   DelayedInit.schedule(() => Foo.init(), this, "Foo", 5000);
     25 *
     26 * In the example above, Foo.init will run automatically when the message loop
     27 * becomes idle, when |this.Foo| is accessed, or when 5000ms has elapsed,
     28 * whichever comes first.
     29 *
     30 * It may be simpler to have a wrapper for DelayedInit.schedule. For example,
     31 *
     32 *   function InitLater(fn, obj, name) {
     33 *     return DelayedInit.schedule(fn, obj, name, 5000); // constant max wait
     34 *   }
     35 *   InitLater(() => Foo.init());
     36 *   InitLater(() => Bar.init(), this, "Bar");
     37 */
     38 export var DelayedInit = {
     39  schedule(fn, object, name, maxWait) {
     40    return Impl.scheduleInit(fn, object, name, maxWait);
     41  },
     42 
     43  scheduleList(fns, maxWait) {
     44    for (const fn of fns) {
     45      Impl.scheduleInit(fn, null, null, maxWait);
     46    }
     47  },
     48 };
     49 
     50 // Maximum duration for each idling period. Pending inits are run until this
     51 // duration is exceeded; then we wait for next idling period.
     52 const MAX_IDLE_RUN_MS = 5;
     53 
     54 var Impl = {
     55  pendingInits: [],
     56 
     57  onIdle() {
     58    const startTime = ChromeUtils.now();
     59    let time = startTime;
     60    let nextDue;
     61 
     62    // Go through all the pending inits. Even if we don't run them,
     63    // we still need to find out when the next timeout should be.
     64    for (const init of this.pendingInits) {
     65      if (init.complete) {
     66        continue;
     67      }
     68 
     69      if (time - startTime < MAX_IDLE_RUN_MS) {
     70        init.maybeInit();
     71        time = ChromeUtils.now();
     72      } else {
     73        // We ran out of time; find when the next closest due time is.
     74        nextDue = nextDue ? Math.min(nextDue, init.due) : init.due;
     75      }
     76    }
     77 
     78    // Get rid of completed ones.
     79    this.pendingInits = this.pendingInits.filter(init => !init.complete);
     80 
     81    if (nextDue !== undefined) {
     82      // Schedule the next idle, if we still have pending inits.
     83      ChromeUtils.idleDispatch(() => this.onIdle(), {
     84        timeout: Math.max(0, nextDue - time),
     85      });
     86    }
     87  },
     88 
     89  addPendingInit(fn, wait) {
     90    const init = {
     91      fn,
     92      due: ChromeUtils.now() + wait,
     93      complete: false,
     94      maybeInit() {
     95        if (this.complete) {
     96          return false;
     97        }
     98        this.complete = true;
     99        this.fn.call();
    100        this.fn = null;
    101        return true;
    102      },
    103    };
    104 
    105    if (!this.pendingInits.length) {
    106      // Schedule for the first idle.
    107      ChromeUtils.idleDispatch(() => this.onIdle(), { timeout: wait });
    108    }
    109    this.pendingInits.push(init);
    110    return init;
    111  },
    112 
    113  scheduleInit(fn, object, name, wait) {
    114    const init = this.addPendingInit(fn, wait);
    115 
    116    if (!object || !name) {
    117      // No lazy getter needed.
    118      return;
    119    }
    120 
    121    // Get any existing information about the property.
    122    let prop = Object.getOwnPropertyDescriptor(object, name) || {
    123      configurable: true,
    124      enumerable: true,
    125      writable: true,
    126    };
    127 
    128    if (!prop.configurable) {
    129      // Object.defineProperty won't work, so just perform init here.
    130      init.maybeInit();
    131      return;
    132    }
    133 
    134    // Define proxy getter/setter that will call first initializer first,
    135    // before delegating the get/set to the original target.
    136    Object.defineProperty(object, name, {
    137      get: function proxy_getter() {
    138        init.maybeInit();
    139 
    140        // If the initializer actually ran, it may have replaced our proxy
    141        // property with a real one, so we need to reload he property.
    142        const newProp = Object.getOwnPropertyDescriptor(object, name);
    143        if (newProp.get !== proxy_getter) {
    144          // Set prop if newProp doesn't refer to our proxy property.
    145          prop = newProp;
    146        } else {
    147          // Otherwise, reset to the original property.
    148          Object.defineProperty(object, name, prop);
    149        }
    150 
    151        if (prop.get) {
    152          return prop.get.call(object);
    153        }
    154        return prop.value;
    155      },
    156      set(newVal) {
    157        init.maybeInit();
    158 
    159        // Since our initializer already ran,
    160        // we can get rid of our proxy property.
    161        if (prop.get || prop.set) {
    162          Object.defineProperty(object, name, prop);
    163          prop.set.call(object);
    164          return;
    165        }
    166 
    167        prop.value = newVal;
    168        Object.defineProperty(object, name, prop);
    169      },
    170      configurable: true,
    171      enumerable: true,
    172    });
    173  },
    174 };