summaryrefslogtreecommitdiffstats
path: root/mobile/android/modules/DelayedInit.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/modules/DelayedInit.jsm')
-rw-r--r--mobile/android/modules/DelayedInit.jsm177
1 files changed, 177 insertions, 0 deletions
diff --git a/mobile/android/modules/DelayedInit.jsm b/mobile/android/modules/DelayedInit.jsm
new file mode 100644
index 000000000..7c33a3ede
--- /dev/null
+++ b/mobile/android/modules/DelayedInit.jsm
@@ -0,0 +1,177 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict"
+
+/*globals MessageLoop */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+this.EXPORTED_SYMBOLS = ["DelayedInit"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "MessageLoop",
+ "@mozilla.org/message-loop;1",
+ "nsIMessageLoop");
+
+/**
+ * Use DelayedInit to schedule initializers to run some time after startup.
+ * Initializers are added to a list of pending inits. Whenever the main thread
+ * message loop is idle, DelayedInit will start running initializers from the
+ * pending list. To prevent monopolizing the message loop, every idling period
+ * has a maximum duration. When that's reached, we give up the message loop and
+ * wait for the next idle.
+ *
+ * DelayedInit is compatible with lazy getters like those from XPCOMUtils. When
+ * the lazy getter is first accessed, its corresponding initializer is run
+ * automatically if it hasn't been run already. Each initializer also has a
+ * maximum wait parameter that specifies a mandatory timeout; when the timeout
+ * is reached, the initializer is forced to run.
+ *
+ * DelayedInit.schedule(() => Foo.init(), null, null, 5000);
+ *
+ * In the example above, Foo.init will run automatically when the message loop
+ * becomes idle, or when 5000ms has elapsed, whichever comes first.
+ *
+ * DelayedInit.schedule(() => Foo.init(), this, "Foo", 5000);
+ *
+ * In the example above, Foo.init will run automatically when the message loop
+ * becomes idle, when |this.Foo| is accessed, or when 5000ms has elapsed,
+ * whichever comes first.
+ *
+ * It may be simpler to have a wrapper for DelayedInit.schedule. For example,
+ *
+ * function InitLater(fn, obj, name) {
+ * return DelayedInit.schedule(fn, obj, name, 5000); // constant max wait
+ * }
+ * InitLater(() => Foo.init());
+ * InitLater(() => Bar.init(), this, "Bar");
+ */
+var DelayedInit = {
+ schedule: function (fn, object, name, maxWait) {
+ return Impl.scheduleInit(fn, object, name, maxWait);
+ },
+};
+
+// Maximum duration for each idling period. Pending inits are run until this
+// duration is exceeded; then we wait for next idling period.
+const MAX_IDLE_RUN_MS = 50;
+
+var Impl = {
+ pendingInits: [],
+
+ onIdle: function () {
+ let startTime = Cu.now();
+ let time = startTime;
+ let nextDue;
+
+ // Go through all the pending inits. Even if we don't run them,
+ // we still need to find out when the next timeout should be.
+ for (let init of this.pendingInits) {
+ if (init.complete) {
+ continue;
+ }
+
+ if (time - startTime < MAX_IDLE_RUN_MS) {
+ init.maybeInit();
+ time = Cu.now();
+ } else {
+ // We ran out of time; find when the next closest due time is.
+ nextDue = nextDue ? Math.min(nextDue, init.due) : init.due;
+ }
+ }
+
+ // Get rid of completed ones.
+ this.pendingInits = this.pendingInits.filter((init) => !init.complete);
+
+ if (nextDue !== undefined) {
+ // Schedule the next idle, if we still have pending inits.
+ MessageLoop.postIdleTask(() => this.onIdle(),
+ Math.max(0, nextDue - time));
+ }
+ },
+
+ addPendingInit: function (fn, wait) {
+ let init = {
+ fn: fn,
+ due: Cu.now() + wait,
+ complete: false,
+ maybeInit: function () {
+ if (this.complete) {
+ return false;
+ }
+ this.complete = true;
+ this.fn.call();
+ this.fn = null;
+ return true;
+ },
+ };
+
+ if (!this.pendingInits.length) {
+ // Schedule for the first idle.
+ MessageLoop.postIdleTask(() => this.onIdle(), wait);
+ }
+ this.pendingInits.push(init);
+ return init;
+ },
+
+ scheduleInit: function (fn, object, name, wait) {
+ let init = this.addPendingInit(fn, wait);
+
+ if (!object || !name) {
+ // No lazy getter needed.
+ return;
+ }
+
+ // Get any existing information about the property.
+ let prop = Object.getOwnPropertyDescriptor(object, name) ||
+ { configurable: true, enumerable: true, writable: true };
+
+ if (!prop.configurable) {
+ // Object.defineProperty won't work, so just perform init here.
+ init.maybeInit();
+ return;
+ }
+
+ // Define proxy getter/setter that will call first initializer first,
+ // before delegating the get/set to the original target.
+ Object.defineProperty(object, name, {
+ get: function proxy_getter() {
+ init.maybeInit();
+
+ // If the initializer actually ran, it may have replaced our proxy
+ // property with a real one, so we need to reload he property.
+ let newProp = Object.getOwnPropertyDescriptor(object, name);
+ if (newProp.get !== proxy_getter) {
+ // Set prop if newProp doesn't refer to our proxy property.
+ prop = newProp;
+ } else {
+ // Otherwise, reset to the original property.
+ Object.defineProperty(object, name, prop);
+ }
+
+ if (prop.get) {
+ return prop.get.call(object);
+ }
+ return prop.value;
+ },
+ set: function (newVal) {
+ init.maybeInit();
+
+ // Since our initializer already ran,
+ // we can get rid of our proxy property.
+ if (prop.get || prop.set) {
+ Object.defineProperty(object, name, prop);
+ return prop.set.call(object);
+ }
+
+ prop.value = newVal;
+ Object.defineProperty(object, name, prop);
+ return newVal;
+ },
+ configurable: true,
+ enumerable: true,
+ });
+ }
+};