ext/Sync.js

215 lines, 63 LOC, 60 covered (95%)

38 1
/* ***** BEGIN LICENSE BLOCK *****
2
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3
 *
4
 * The contents of this file are subject to the Mozilla Public License Version
5
 * 1.1 (the "License"); you may not use this file except in compliance with
6
 * the License. You may obtain a copy of the License at
7
 * http://www.mozilla.org/MPL/
8
 *
9
 * Software distributed under the License is distributed on an "AS IS" basis,
10
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11
 * for the specific language governing rights and limitations under the
12
 * License.
13
 *
14
 * The Original Code is Weave.
15
 *
16
 * The Initial Developer of the Original Code is Mozilla.
17
 * Portions created by the Initial Developer are Copyright (C) 2009
18
 * the Initial Developer. All Rights Reserved.
19
 *
20
 * Contributor(s):
21
 *   Edward Lee <edilee@mozilla.com>
22
 *   Dan Mills <thunder@mozilla.com>
23
 *   Myk Melez <myk@mozilla.org>
24
 *
25
 * Alternatively, the contents of this file may be used under the terms of
26
 * either the GNU General Public License Version 2 or later (the "GPL"), or
27
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28
 * in which case the provisions of the GPL or the LGPL are applicable instead
29
 * of those above. If you wish to allow use of your version of this file only
30
 * under the terms of either the GPL or the LGPL, and not to allow others to
31
 * use your version of this file under the terms of the MPL, indicate your
32
 * decision by deleting the provisions above and replace them with the notice
33
 * and other provisions required by the GPL or the LGPL. If you do not delete
34
 * the provisions above, a recipient may use your version of this file under
35
 * the terms of any one of the MPL, the GPL or the LGPL.
36
 *
37
 * ***** END LICENSE BLOCK ***** */
38
266 39
let EXPORTED_SYMBOLS = ["Sync"];
40
152 41
const Cc = Components.classes;
152 42
const Ci = Components.interfaces;
152 43
const Cr = Components.results;
152 44
const Cu = Components.utils;
45
46
// Define some constants to specify various sync. callback states
152 47
const CB_READY = {};
152 48
const CB_COMPLETE = {};
152 49
const CB_FAIL = {};
50
51
// Share a secret only for functions in this file to prevent outside access
152 52
const SECRET = {};
53
54
/**
55
 * Check if the app is ready (not quitting)
56
 */
82 57
function checkAppReady() {
58
  // Watch for app-quit notification to stop any sync. calls
18 59
  let os = Cc["@mozilla.org/observer-service;1"].
24 60
    getService(Ci.nsIObserverService);
18 61
  os.addObserver({
18 62
    observe: function observe() {
63
      // Now that the app is quitting, make checkAppReady throw
64
      checkAppReady = function() {
65
        throw Components.Exception("App. Quitting", Cr.NS_ERROR_ABORT);
66
      };
67
      os.removeObserver(this, "quit-application");
68
    }
24 69
  }, "quit-application", false);
70
71
  // In the common case, checkAppReady just returns true
30636 72
  return (checkAppReady = function() true)();
73
};
74
75
/**
76
 * Create a callback that remembers state like whether it's been called
77
 */
3828 78
function makeCallback() {
79
  // Initialize private callback data to prepare to be called
3752 80
  let _ = {
7504 81
    state: CB_READY,
15008 82
    value: null
83
  };
84
85
  // The main callback remembers the value it's passed and that it got data
11255 86
  let onComplete = function makeCallback_onComplete(data) {
11253 87
    _.state = CB_COMPLETE;
15004 88
    _.value = data;
89
  };
90
91
  // Only allow access to the private data if the secret matches
37520 92
  onComplete._ = function onComplete__(secret) secret == SECRET ? _ : {};
93
94
  // Allow an alternate callback to trigger an exception to be thrown
11257 95
  onComplete.throw = function onComplete_throw(data) {
3 96
    _.state = CB_FAIL;
3 97
    _.value = data;
98
99
    // Cause the caller to get an exception and stop execution
2 100
    throw data;
101
  };
102
7504 103
  return onComplete;
104
}
105
106
/**
107
 * Make a synchronous version of the function object that will be called with
108
 * the provided thisArg.
109
 *
110
 * @param func {Function}
111
 *        The asynchronous function to make a synchronous function
112
 * @param thisArg {Object} [optional]
113
 *        The object that the function accesses with "this"
114
 * @param callback {Function} [optional] [internal]
115
 *        The callback that will trigger the end of the async. call
116
 * @usage let ret = Sync(asyncFunc, obj)(arg1, arg2);
117
 * @usage let ret = Sync(ignoreThisFunc)(arg1, arg2);
118
 * @usage let sync = Sync(async); let ret = sync(arg1, arg2);
119
 */
269 120
function Sync(func, thisArg, callback) {
4138 121
  return function syncFunc(/* arg1, arg2, ... */) {
122
    // Grab the current thread so we can make it give up priority
22512 123
    let thread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
124
125
    // Save the original arguments into an array
18760 126
    let args = Array.slice(arguments);
127
7504 128
    let instanceCallback = callback;
129
    // We need to create a callback and insert it if we weren't given one
11256 130
    if (instanceCallback == null) {
131
      // Create a new callback for this invocation instance and pass it in
10791 132
      instanceCallback = makeCallback();
17985 133
      args.unshift(instanceCallback);
134
    }
135
136
    // Call the async function bound to thisArg with the passed args
22512 137
    func.apply(thisArg, args);
138
139
    // Keep waiting until our callback is triggered unless the app is quitting
18760 140
    let callbackData = instanceCallback._(SECRET);
71400 141
    while (checkAppReady() && callbackData.state == CB_READY)
32240 142
      thread.processNextEvent(true);
143
144
    // Reset the state of the callback to prepare for another call
7504 145
    let state = callbackData.state;
11256 146
    callbackData.state = CB_READY;
147
148
    // Throw the value the callback decided to fail with
11256 149
    if (state == CB_FAIL)
2 150
      throw callbackData.value;
151
152
    // Return the value passed to the callback
7502 153
    return callbackData.value;
154
  };
155
}
156
157
/**
158
 * Make a synchronous version of an async. function and the callback to trigger
159
 * the end of the async. call.
160
 *
161
 * @param func {Function}
162
 *        The asynchronous function to make a synchronous function
163
 * @param thisArg {Object} [optional]
164
 *        The object that the function accesses with "this"
165
 * @usage let [sync, cb] = Sync.withCb(async); let ret = sync(arg1, arg2, cb);
166
 */
307 167
Sync.withCb = function Sync_withCb(func, thisArg) {
465 168
  let cb = makeCallback();
1240 169
  return [Sync(func, thisArg, cb), cb];
170
};
171
172
/**
173
 * Set a timer, simulating the API for the window.setTimeout call.
174
 * This only simulates the API for the version of the call that accepts
175
 * a function as its first argument and no additional parameters,
176
 * and it doesn't return the timeout ID.
177
 *
178
 * @param func {Function}
179
 *        the function to call after the delay
180
 * @param delay {Number}
181
 *        the number of milliseconds to wait
182
 */
3673 183
function setTimeout(func, delay) {
25179 184
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
3597 185
  let callback = {
17985 186
    notify: function notify() {
187
      // This line actually just keeps a reference to timer (prevent GC)
10791 188
      timer = null;
189
190
      // Call the function so that "this" is global
14388 191
      func();
192
    }
193
  }
35970 194
  timer.initWithCallback(callback, delay, Ci.nsITimer.TYPE_ONE_SHOT);
195
}
196
3673 197
function sleep(callback, milliseconds) {
21582 198
  setTimeout(callback, milliseconds);
199
}
200
201
/**
202
 * Sleep the specified number of milliseconds, pausing execution of the caller
203
 * without halting the current thread.
204
 * For example, the following code pauses 1000ms between dumps:
205
 *
206
 *   dump("Wait for it...\n");
207
 *   Sync.sleep(1000);
208
 *   dump("Wait for it...\n");
209
 *   Sync.sleep(1000);
210
 *   dump("What are you waiting for?!\n");
211
 *
212
 * @param milliseconds {Number}
213
 *        The number of milliseconds to sleep
214
 */
266 215
Sync.sleep = Sync(sleep);