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); |