|
1 /** |
|
2 * Test TCPSocket.js by creating an XPCOM-style server socket, then sending |
|
3 * data in both directions and making sure each side receives their data |
|
4 * correctly and with the proper events. |
|
5 * |
|
6 * This test is derived from netwerk/test/unit/test_socks.js, except we don't |
|
7 * involve a subprocess. |
|
8 * |
|
9 * Future work: |
|
10 * - SSL. see https://bugzilla.mozilla.org/show_bug.cgi?id=466524 |
|
11 * https://bugzilla.mozilla.org/show_bug.cgi?id=662180 |
|
12 * Alternatively, mochitests could be used. |
|
13 * - Testing overflow logic. |
|
14 * |
|
15 **/ |
|
16 |
|
17 const Cc = Components.classes; |
|
18 const Ci = Components.interfaces; |
|
19 const Cr = Components.results; |
|
20 const Cu = Components.utils; |
|
21 const CC = Components.Constructor; |
|
22 |
|
23 /** |
|
24 * |
|
25 * Constants |
|
26 * |
|
27 */ |
|
28 |
|
29 // Some binary data to send. |
|
30 const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0], |
|
31 DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length), |
|
32 TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER), |
|
33 HELLO_WORLD = "hlo wrld. ", |
|
34 BIG_ARRAY = new Array(65539), |
|
35 BIG_ARRAY_2 = new Array(65539); |
|
36 |
|
37 TYPED_DATA_ARRAY.set(DATA_ARRAY, 0); |
|
38 |
|
39 for (var i_big = 0; i_big < BIG_ARRAY.length; i_big++) { |
|
40 BIG_ARRAY[i_big] = Math.floor(Math.random() * 256); |
|
41 BIG_ARRAY_2[i_big] = Math.floor(Math.random() * 256); |
|
42 } |
|
43 |
|
44 const BIG_ARRAY_BUFFER = new ArrayBuffer(BIG_ARRAY.length), |
|
45 BIG_ARRAY_BUFFER_2 = new ArrayBuffer(BIG_ARRAY_2.length); |
|
46 const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY_BUFFER), |
|
47 BIG_TYPED_ARRAY_2 = new Uint8Array(BIG_ARRAY_BUFFER_2); |
|
48 BIG_TYPED_ARRAY.set(BIG_ARRAY); |
|
49 BIG_TYPED_ARRAY_2.set(BIG_ARRAY_2); |
|
50 |
|
51 const ServerSocket = CC("@mozilla.org/network/server-socket;1", |
|
52 "nsIServerSocket", |
|
53 "init"), |
|
54 InputStreamPump = CC("@mozilla.org/network/input-stream-pump;1", |
|
55 "nsIInputStreamPump", |
|
56 "init"), |
|
57 BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", |
|
58 "nsIBinaryInputStream", |
|
59 "setInputStream"), |
|
60 BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1", |
|
61 "nsIBinaryOutputStream", |
|
62 "setOutputStream"), |
|
63 TCPSocket = new (CC("@mozilla.org/tcp-socket;1", |
|
64 "nsIDOMTCPSocket"))(); |
|
65 |
|
66 const gInChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime) |
|
67 .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; |
|
68 |
|
69 Cu.import("resource://gre/modules/Services.jsm"); |
|
70 |
|
71 /** |
|
72 * |
|
73 * Helper functions |
|
74 * |
|
75 */ |
|
76 |
|
77 function get_platform() { |
|
78 var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"] |
|
79 .getService(Components.interfaces.nsIXULRuntime); |
|
80 return xulRuntime.OS; |
|
81 } |
|
82 |
|
83 function is_content() { |
|
84 return this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime) |
|
85 .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; |
|
86 } |
|
87 |
|
88 /** |
|
89 * Spin up a listening socket and associate at most one live, accepted socket |
|
90 * with ourselves. |
|
91 */ |
|
92 function TestServer() { |
|
93 this.listener = ServerSocket(-1, true, -1); |
|
94 do_print('server: listening on', this.listener.port); |
|
95 this.listener.asyncListen(this); |
|
96 |
|
97 this.binaryInput = null; |
|
98 this.input = null; |
|
99 this.binaryOutput = null; |
|
100 this.output = null; |
|
101 |
|
102 this.onconnect = null; |
|
103 this.ondata = null; |
|
104 this.onclose = null; |
|
105 } |
|
106 |
|
107 TestServer.prototype = { |
|
108 onSocketAccepted: function(socket, trans) { |
|
109 if (this.input) |
|
110 do_throw("More than one live connection!?"); |
|
111 |
|
112 do_print('server: got client connection'); |
|
113 this.input = trans.openInputStream(0, 0, 0); |
|
114 this.binaryInput = new BinaryInputStream(this.input); |
|
115 this.output = trans.openOutputStream(0, 0, 0); |
|
116 this.binaryOutput = new BinaryOutputStream(this.output); |
|
117 |
|
118 new InputStreamPump(this.input, -1, -1, 0, 0, false).asyncRead(this, null); |
|
119 |
|
120 if (this.onconnect) |
|
121 this.onconnect(); |
|
122 else |
|
123 do_throw("Received unexpected connection!"); |
|
124 }, |
|
125 |
|
126 onStopListening: function(socket) { |
|
127 }, |
|
128 |
|
129 onDataAvailable: function(request, context, inputStream, offset, count) { |
|
130 var readData = this.binaryInput.readByteArray(count); |
|
131 if (this.ondata) { |
|
132 try { |
|
133 this.ondata(readData); |
|
134 } catch(ex) { |
|
135 // re-throw if this is from do_throw |
|
136 if (ex === Cr.NS_ERROR_ABORT) |
|
137 throw ex; |
|
138 // log if there was a test problem |
|
139 do_print('Caught exception: ' + ex + '\n' + ex.stack); |
|
140 do_throw('test is broken; bad ondata handler; see above'); |
|
141 } |
|
142 } else { |
|
143 do_throw('Received ' + count + ' bytes of unexpected data!'); |
|
144 } |
|
145 }, |
|
146 |
|
147 onStartRequest: function(request, context) { |
|
148 }, |
|
149 |
|
150 onStopRequest: function(request, context, status) { |
|
151 if (this.onclose) |
|
152 this.onclose(); |
|
153 else |
|
154 do_throw("Received unexpected close!"); |
|
155 }, |
|
156 |
|
157 close: function() { |
|
158 this.binaryInput.close(); |
|
159 this.binaryOutput.close(); |
|
160 }, |
|
161 |
|
162 /** |
|
163 * Forget about the socket we knew about before. |
|
164 */ |
|
165 reset: function() { |
|
166 this.binaryInput = null; |
|
167 this.input = null; |
|
168 this.binaryOutput = null; |
|
169 this.output = null; |
|
170 }, |
|
171 }; |
|
172 |
|
173 function makeSuccessCase(name) { |
|
174 return function() { |
|
175 do_print('got expected: ' + name); |
|
176 run_next_test(); |
|
177 }; |
|
178 } |
|
179 |
|
180 function makeJointSuccess(names) { |
|
181 let funcs = {}, successCount = 0; |
|
182 names.forEach(function(name) { |
|
183 funcs[name] = function() { |
|
184 do_print('got expected: ' + name); |
|
185 if (++successCount === names.length) |
|
186 run_next_test(); |
|
187 }; |
|
188 }); |
|
189 return funcs; |
|
190 } |
|
191 |
|
192 function makeFailureCase(name) { |
|
193 return function() { |
|
194 let argstr; |
|
195 if (arguments.length) { |
|
196 argstr = '(args: ' + |
|
197 Array.map(arguments, function(x) { return x.data + ""; }).join(" ") + ')'; |
|
198 } |
|
199 else { |
|
200 argstr = '(no arguments)'; |
|
201 } |
|
202 do_throw('got unexpected: ' + name + ' ' + argstr); |
|
203 }; |
|
204 } |
|
205 |
|
206 function makeExpectData(name, expectedData, fromEvent, callback) { |
|
207 let dataBuffer = fromEvent ? null : [], done = false; |
|
208 let dataBufferView = null; |
|
209 return function(receivedData) { |
|
210 if (receivedData.data) { |
|
211 receivedData = receivedData.data; |
|
212 } |
|
213 let recvLength = receivedData.byteLength !== undefined ? |
|
214 receivedData.byteLength : receivedData.length; |
|
215 |
|
216 if (fromEvent) { |
|
217 if (dataBuffer) { |
|
218 let newBuffer = new ArrayBuffer(dataBuffer.byteLength + recvLength); |
|
219 let newBufferView = new Uint8Array(newBuffer); |
|
220 newBufferView.set(dataBufferView, 0); |
|
221 newBufferView.set(receivedData, dataBuffer.byteLength); |
|
222 dataBuffer = newBuffer; |
|
223 dataBufferView = newBufferView; |
|
224 } |
|
225 else { |
|
226 dataBuffer = receivedData; |
|
227 dataBufferView = new Uint8Array(dataBuffer); |
|
228 } |
|
229 } |
|
230 else { |
|
231 dataBuffer = dataBuffer.concat(receivedData); |
|
232 } |
|
233 do_print(name + ' received ' + recvLength + ' bytes'); |
|
234 |
|
235 if (done) |
|
236 do_throw(name + ' Received data event when already done!'); |
|
237 |
|
238 let dataView = dataBuffer.byteLength !== undefined ? new Uint8Array(dataBuffer) : dataBuffer; |
|
239 if (dataView.length >= expectedData.length) { |
|
240 // check the bytes are equivalent |
|
241 for (let i = 0; i < expectedData.length; i++) { |
|
242 if (dataView[i] !== expectedData[i]) { |
|
243 do_throw(name + ' Received mismatched character at position ' + i); |
|
244 } |
|
245 } |
|
246 if (dataView.length > expectedData.length) |
|
247 do_throw(name + ' Received ' + dataView.length + ' bytes but only expected ' + |
|
248 expectedData.length + ' bytes.'); |
|
249 |
|
250 done = true; |
|
251 if (callback) { |
|
252 callback(); |
|
253 } else { |
|
254 run_next_test(); |
|
255 } |
|
256 } |
|
257 }; |
|
258 } |
|
259 |
|
260 var server = null, sock = null, failure_drain = null; |
|
261 |
|
262 /** |
|
263 * |
|
264 * Test functions |
|
265 * |
|
266 */ |
|
267 |
|
268 /** |
|
269 * Connect the socket to the server. This test is added as the first |
|
270 * test, and is also added after every test which results in the socket |
|
271 * being closed. |
|
272 */ |
|
273 |
|
274 function connectSock() { |
|
275 server.reset(); |
|
276 var yayFuncs = makeJointSuccess(['serveropen', 'clientopen']); |
|
277 |
|
278 sock = TCPSocket.open( |
|
279 '127.0.0.1', server.listener.port, |
|
280 { binaryType: 'arraybuffer' }); |
|
281 |
|
282 sock.onopen = yayFuncs.clientopen; |
|
283 sock.ondrain = null; |
|
284 sock.ondata = makeFailureCase('data'); |
|
285 sock.onerror = makeFailureCase('error'); |
|
286 sock.onclose = makeFailureCase('close'); |
|
287 |
|
288 server.onconnect = yayFuncs.serveropen; |
|
289 server.ondata = makeFailureCase('serverdata'); |
|
290 server.onclose = makeFailureCase('serverclose'); |
|
291 } |
|
292 |
|
293 /** |
|
294 * Test that sending a small amount of data works, and that buffering |
|
295 * does not take place for this small amount of data. |
|
296 */ |
|
297 |
|
298 function sendData() { |
|
299 server.ondata = makeExpectData('serverdata', DATA_ARRAY); |
|
300 if (!sock.send(DATA_ARRAY_BUFFER)) { |
|
301 do_throw("send should not have buffered such a small amount of data"); |
|
302 } |
|
303 } |
|
304 |
|
305 /** |
|
306 * Test that sending a large amount of data works, that buffering |
|
307 * takes place (send returns true), and that ondrain is called once |
|
308 * the data has been sent. |
|
309 */ |
|
310 |
|
311 function sendBig() { |
|
312 var yays = makeJointSuccess(['serverdata', 'clientdrain']), |
|
313 amount = 0; |
|
314 |
|
315 server.ondata = function (data) { |
|
316 amount += data.length; |
|
317 if (amount === BIG_TYPED_ARRAY.length) { |
|
318 yays.serverdata(); |
|
319 } |
|
320 }; |
|
321 sock.ondrain = function(evt) { |
|
322 if (sock.bufferedAmount) { |
|
323 do_throw("sock.bufferedAmount was > 0 in ondrain"); |
|
324 } |
|
325 yays.clientdrain(evt); |
|
326 } |
|
327 if (sock.send(BIG_ARRAY_BUFFER)) { |
|
328 do_throw("expected sock.send to return false on large buffer send"); |
|
329 } |
|
330 } |
|
331 |
|
332 /** |
|
333 * Test that data sent from the server correctly fires the ondata |
|
334 * callback on the client side. |
|
335 */ |
|
336 |
|
337 function receiveData() { |
|
338 server.ondata = makeFailureCase('serverdata'); |
|
339 sock.ondata = makeExpectData('data', DATA_ARRAY, true); |
|
340 |
|
341 server.binaryOutput.writeByteArray(DATA_ARRAY, DATA_ARRAY.length); |
|
342 } |
|
343 |
|
344 /** |
|
345 * Test that when the server closes the connection, the onclose callback |
|
346 * is fired on the client side. |
|
347 */ |
|
348 |
|
349 function serverCloses() { |
|
350 // we don't really care about the server's close event, but we do want to |
|
351 // make sure it happened for sequencing purposes. |
|
352 var yayFuncs = makeJointSuccess(['clientclose', 'serverclose']); |
|
353 sock.ondata = makeFailureCase('data'); |
|
354 sock.onclose = yayFuncs.clientclose; |
|
355 server.onclose = yayFuncs.serverclose; |
|
356 |
|
357 server.close(); |
|
358 } |
|
359 |
|
360 /** |
|
361 * Test that when the client closes the connection, the onclose callback |
|
362 * is fired on the server side. |
|
363 */ |
|
364 |
|
365 function clientCloses() { |
|
366 // we want to make sure the server heard the close and also that the client's |
|
367 // onclose event fired for consistency. |
|
368 var yayFuncs = makeJointSuccess(['clientclose', 'serverclose']); |
|
369 server.onclose = yayFuncs.serverclose; |
|
370 sock.onclose = yayFuncs.clientclose; |
|
371 |
|
372 sock.close(); |
|
373 } |
|
374 |
|
375 /** |
|
376 * Send a large amount of data and immediately call close |
|
377 */ |
|
378 |
|
379 function bufferedClose() { |
|
380 var yays = makeJointSuccess(['serverdata', 'clientclose', 'serverclose']); |
|
381 server.ondata = makeExpectData( |
|
382 "ondata", BIG_TYPED_ARRAY, false, yays.serverdata); |
|
383 server.onclose = yays.serverclose; |
|
384 sock.onclose = yays.clientclose; |
|
385 sock.send(BIG_ARRAY_BUFFER); |
|
386 sock.close(); |
|
387 } |
|
388 |
|
389 /** |
|
390 * Connect to a port we know is not listening so an error is assured, |
|
391 * and make sure that onerror and onclose are fired on the client side. |
|
392 */ |
|
393 |
|
394 function badConnect() { |
|
395 // There's probably nothing listening on tcp port 2. |
|
396 sock = TCPSocket.open('127.0.0.1', 2); |
|
397 |
|
398 sock.onopen = makeFailureCase('open'); |
|
399 sock.ondata = makeFailureCase('data'); |
|
400 |
|
401 let success = makeSuccessCase('error'); |
|
402 let gotError = false; |
|
403 sock.onerror = function(event) { |
|
404 do_check_eq(event.data.name, 'ConnectionRefusedError'); |
|
405 gotError = true; |
|
406 }; |
|
407 sock.onclose = function() { |
|
408 if (!gotError) |
|
409 do_throw('got close without error!'); |
|
410 else |
|
411 success(); |
|
412 }; |
|
413 } |
|
414 |
|
415 /** |
|
416 * Test that calling send with enough data to buffer causes ondrain to |
|
417 * be invoked once the data has been sent, and then test that calling send |
|
418 * and buffering again causes ondrain to be fired again. |
|
419 */ |
|
420 |
|
421 function drainTwice() { |
|
422 let yays = makeJointSuccess( |
|
423 ['ondrain', 'ondrain2', |
|
424 'ondata', 'ondata2', |
|
425 'serverclose', 'clientclose']); |
|
426 let ondrainCalled = false, |
|
427 ondataCalled = false; |
|
428 |
|
429 function maybeSendNextData() { |
|
430 if (!ondrainCalled || !ondataCalled) { |
|
431 // make sure server got data and client got ondrain. |
|
432 return; |
|
433 } |
|
434 |
|
435 server.ondata = makeExpectData( |
|
436 "ondata2", BIG_TYPED_ARRAY_2, false, yays.ondata2); |
|
437 |
|
438 sock.ondrain = yays.ondrain2; |
|
439 |
|
440 if (sock.send(BIG_ARRAY_BUFFER_2)) { |
|
441 do_throw("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering"); |
|
442 } |
|
443 |
|
444 sock.close(); |
|
445 } |
|
446 |
|
447 function clientOndrain() { |
|
448 yays.ondrain(); |
|
449 ondrainCalled = true; |
|
450 maybeSendNextData(); |
|
451 } |
|
452 |
|
453 function serverSideCallback() { |
|
454 yays.ondata(); |
|
455 ondataCalled = true; |
|
456 maybeSendNextData(); |
|
457 } |
|
458 |
|
459 server.onclose = yays.serverclose; |
|
460 server.ondata = makeExpectData( |
|
461 "ondata", BIG_TYPED_ARRAY, false, serverSideCallback); |
|
462 |
|
463 sock.onclose = yays.clientclose; |
|
464 sock.ondrain = clientOndrain; |
|
465 |
|
466 if (sock.send(BIG_ARRAY_BUFFER)) { |
|
467 throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering"); |
|
468 } |
|
469 } |
|
470 |
|
471 function cleanup() { |
|
472 do_print("Cleaning up"); |
|
473 sock.close(); |
|
474 if (!gInChild) |
|
475 Services.prefs.clearUserPref('dom.mozTCPSocket.enabled'); |
|
476 run_next_test(); |
|
477 } |
|
478 |
|
479 /** |
|
480 * Test that calling send with enough data to buffer twice in a row without |
|
481 * waiting for ondrain still results in ondrain being invoked at least once. |
|
482 */ |
|
483 |
|
484 function bufferTwice() { |
|
485 let yays = makeJointSuccess( |
|
486 ['ondata', 'ondrain', 'serverclose', 'clientclose']); |
|
487 |
|
488 let double_array = new Uint8Array(BIG_ARRAY.concat(BIG_ARRAY_2)); |
|
489 server.ondata = makeExpectData( |
|
490 "ondata", double_array, false, yays.ondata); |
|
491 |
|
492 server.onclose = yays.serverclose; |
|
493 sock.onclose = yays.clientclose; |
|
494 |
|
495 sock.ondrain = function () { |
|
496 sock.close(); |
|
497 yays.ondrain(); |
|
498 } |
|
499 |
|
500 if (sock.send(BIG_ARRAY_BUFFER)) { |
|
501 throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering"); |
|
502 } |
|
503 if (sock.send(BIG_ARRAY_BUFFER_2)) { |
|
504 throw new Error("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering on second synchronous call to send"); |
|
505 } |
|
506 } |
|
507 |
|
508 // Test child behavior when child thinks it's buffering but parent doesn't |
|
509 // buffer. |
|
510 // 1. set bufferedAmount of content socket to a value that will make next |
|
511 // send() call return false. |
|
512 // 2. send a small data to make send() return false, but it won't make |
|
513 // parent buffer. |
|
514 // 3. we should get a ondrain. |
|
515 function childbuffered() { |
|
516 let yays = makeJointSuccess(['ondrain', 'serverdata', |
|
517 'clientclose', 'serverclose']); |
|
518 sock.ondrain = function() { |
|
519 yays.ondrain(); |
|
520 sock.close(); |
|
521 }; |
|
522 |
|
523 server.ondata = makeExpectData( |
|
524 'ondata', DATA_ARRAY, false, yays.serverdata); |
|
525 |
|
526 let internalSocket = sock.QueryInterface(Ci.nsITCPSocketInternal); |
|
527 internalSocket.updateBufferedAmount(65535, // almost reach buffering threshold |
|
528 0); |
|
529 if (sock.send(DATA_ARRAY_BUFFER)) { |
|
530 do_throw("expected sock.send to return false."); |
|
531 } |
|
532 |
|
533 sock.onclose = yays.clientclose; |
|
534 server.onclose = yays.serverclose; |
|
535 } |
|
536 |
|
537 // Test child's behavior when send() of child return true but parent buffers |
|
538 // data. |
|
539 // 1. send BIG_ARRAY to make parent buffer. This would make child wait for |
|
540 // drain as well. |
|
541 // 2. set child's bufferedAmount to zero, so child will no longer wait for |
|
542 // drain but parent will dispatch a drain event. |
|
543 // 3. wait for 1 second, to make sure there's no ondrain event dispatched in |
|
544 // child. |
|
545 function childnotbuffered() { |
|
546 let yays = makeJointSuccess(['serverdata', 'clientclose', 'serverclose']); |
|
547 server.ondata = makeExpectData('ondata', BIG_ARRAY, false, yays.serverdata); |
|
548 if (sock.send(BIG_ARRAY_BUFFER)) { |
|
549 do_throw("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering"); |
|
550 } |
|
551 let internalSocket = sock.QueryInterface(Ci.nsITCPSocketInternal); |
|
552 internalSocket.updateBufferedAmount(0, // setting zero will clear waitForDrain in sock. |
|
553 1); |
|
554 |
|
555 // shouldn't get ondrain, even after parent have cleared its buffer. |
|
556 sock.ondrain = makeFailureCase('drain'); |
|
557 sock.onclose = yays.clientclose; |
|
558 server.onclose = yays.serverclose; |
|
559 do_timeout(1000, function() { |
|
560 sock.close(); |
|
561 }); |
|
562 }; |
|
563 |
|
564 // - connect, data and events work both ways |
|
565 add_test(connectSock); |
|
566 add_test(sendData); |
|
567 add_test(sendBig); |
|
568 add_test(receiveData); |
|
569 // - server closes on us |
|
570 add_test(serverCloses); |
|
571 |
|
572 // - connect, we close on the server |
|
573 add_test(connectSock); |
|
574 add_test(clientCloses); |
|
575 |
|
576 // - connect, buffer, close |
|
577 add_test(connectSock); |
|
578 add_test(bufferedClose); |
|
579 |
|
580 if (get_platform() !== "Darwin") { |
|
581 // This test intermittently fails way too often on OS X, for unknown reasons. |
|
582 // Please, diagnose and fix it if you can. |
|
583 // - get an error on an attempt to connect to a non-listening port |
|
584 add_test(badConnect); |
|
585 } |
|
586 |
|
587 // send a buffer, get a drain, send a buffer, get a drain |
|
588 add_test(connectSock); |
|
589 add_test(drainTwice); |
|
590 |
|
591 // send a buffer, get a drain, send a buffer, get a drain |
|
592 add_test(connectSock); |
|
593 add_test(bufferTwice); |
|
594 |
|
595 if (is_content()) { |
|
596 add_test(connectSock); |
|
597 add_test(childnotbuffered); |
|
598 |
|
599 add_test(connectSock); |
|
600 add_test(childbuffered); |
|
601 } |
|
602 |
|
603 // clean up |
|
604 add_test(cleanup); |
|
605 |
|
606 function run_test() { |
|
607 if (!gInChild) |
|
608 Services.prefs.setBoolPref('dom.mozTCPSocket.enabled', true); |
|
609 |
|
610 server = new TestServer(); |
|
611 |
|
612 run_next_test(); |
|
613 } |