no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / netwerk / test / unit / head_servers.js
blobb8087e496fa813170bbe9d769c597177d99d727c
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/. */
5 "use strict";
7 /* import-globals-from head_cache.js */
8 /* import-globals-from head_cookies.js */
9 /* import-globals-from head_channels.js */
11 const { NodeServer } = ChromeUtils.importESModule(
12   "resource://testing-common/httpd.sys.mjs"
14 const { AppConstants } = ChromeUtils.importESModule(
15   "resource://gre/modules/AppConstants.sys.mjs"
18 /* globals require, __dirname, global, Buffer, process */
20 class BaseNodeHTTPServerCode {
21   static globalHandler(req, resp) {
22     let path = new URL(req.url, "http://example.com").pathname;
23     let handler = global.path_handlers[path];
24     if (handler) {
25       return handler(req, resp);
26     }
28     // Didn't find a handler for this path.
29     let response = `<h1> 404 Path not found: ${path}</h1>`;
30     resp.setHeader("Content-Type", "text/html");
31     resp.setHeader("Content-Length", response.length);
32     resp.writeHead(404);
33     resp.end(response);
34     return undefined;
35   }
38 class ADB {
39   static async stopForwarding(port) {
40     return this.forwardPort(port, true);
41   }
43   static async forwardPort(port, remove = false) {
44     if (!process.env.MOZ_ANDROID_DATA_DIR) {
45       // Not android, or we don't know how to do the forwarding
46       return true;
47     }
48     // When creating a server on Android we must make sure that the port
49     // is forwarded from the host machine to the emulator.
50     let adb_path = "adb";
51     if (process.env.MOZ_FETCHES_DIR) {
52       adb_path = `${process.env.MOZ_FETCHES_DIR}/android-sdk-linux/platform-tools/adb`;
53     }
55     let command = `${adb_path} reverse tcp:${port} tcp:${port}`;
56     if (remove) {
57       command = `${adb_path} reverse --remove tcp:${port}`;
58       return true;
59     }
61     try {
62       await new Promise((resolve, reject) => {
63         const { exec } = require("child_process");
64         exec(command, (error, stdout, stderr) => {
65           if (error) {
66             console.log(`error: ${error.message}`);
67             reject(error);
68           } else if (stderr) {
69             console.log(`stderr: ${stderr}`);
70             reject(stderr);
71           } else {
72             // console.log(`stdout: ${stdout}`);
73             resolve();
74           }
75         });
76       });
77     } catch (error) {
78       console.log(`Command failed: ${error}`);
79       return false;
80     }
82     return true;
83   }
85   static async listenAndForwardPort(server, port) {
86     let retryCount = 0;
87     const maxRetries = 10;
89     while (retryCount < maxRetries) {
90       await server.listen(port);
91       let serverPort = server.address().port;
92       let res = await ADB.forwardPort(serverPort);
94       if (res) {
95         return serverPort;
96       }
98       retryCount++;
99       console.log(
100         `Port forwarding failed. Retrying (${retryCount}/${maxRetries})...`
101       );
102       server.close();
103       // eslint-disable-next-line no-undef
104       await new Promise(resolve => setTimeout(resolve, 500));
105     }
107     return -1;
108   }
111 class BaseNodeServer {
112   protocol() {
113     return this._protocol;
114   }
115   version() {
116     return this._version;
117   }
118   origin() {
119     return `${this.protocol()}://localhost:${this.port()}`;
120   }
121   port() {
122     return this._port;
123   }
124   domain() {
125     return `localhost`;
126   }
128   /// Stops the server
129   async stop() {
130     if (this.processId) {
131       await this.execute(`ADB.stopForwarding(${this.port()})`);
132       await NodeServer.kill(this.processId);
133       this.processId = undefined;
134     }
135   }
137   /// Executes a command in the context of the node server
138   async execute(command) {
139     return NodeServer.execute(this.processId, command);
140   }
142   /// @path : string - the path on the server that we're handling. ex: /path
143   /// @handler : function(req, resp, url) - function that processes request and
144   ///     emits a response.
145   async registerPathHandler(path, handler) {
146     return this.execute(
147       `global.path_handlers["${path}"] = ${handler.toString()}`
148     );
149   }
152 // HTTP
154 class NodeHTTPServerCode extends BaseNodeHTTPServerCode {
155   static async startServer(port) {
156     const http = require("http");
157     global.server = http.createServer(BaseNodeHTTPServerCode.globalHandler);
159     let serverPort = await ADB.listenAndForwardPort(global.server, port);
160     return serverPort;
161   }
164 class NodeHTTPServer extends BaseNodeServer {
165   _protocol = "http";
166   _version = "http/1.1";
167   /// Starts the server
168   /// @port - default 0
169   ///    when provided, will attempt to listen on that port.
170   async start(port = 0) {
171     this.processId = await NodeServer.fork();
173     await this.execute(BaseNodeHTTPServerCode);
174     await this.execute(NodeHTTPServerCode);
175     await this.execute(ADB);
176     this._port = await this.execute(`NodeHTTPServerCode.startServer(${port})`);
177     await this.execute(`global.path_handlers = {};`);
178   }
181 // HTTPS
183 class NodeHTTPSServerCode extends BaseNodeHTTPServerCode {
184   static async startServer(port) {
185     const fs = require("fs");
186     const options = {
187       key: fs.readFileSync(__dirname + "/http2-cert.key"),
188       cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
189     };
190     const https = require("https");
191     global.server = https.createServer(
192       options,
193       BaseNodeHTTPServerCode.globalHandler
194     );
196     let serverPort = await ADB.listenAndForwardPort(global.server, port);
197     return serverPort;
198   }
201 class NodeHTTPSServer extends BaseNodeServer {
202   _protocol = "https";
203   _version = "http/1.1";
204   /// Starts the server
205   /// @port - default 0
206   ///    when provided, will attempt to listen on that port.
207   async start(port = 0) {
208     this.processId = await NodeServer.fork();
210     await this.execute(BaseNodeHTTPServerCode);
211     await this.execute(NodeHTTPSServerCode);
212     await this.execute(ADB);
213     this._port = await this.execute(`NodeHTTPSServerCode.startServer(${port})`);
214     await this.execute(`global.path_handlers = {};`);
215   }
218 // HTTP2
220 class NodeHTTP2ServerCode extends BaseNodeHTTPServerCode {
221   static async startServer(port) {
222     const fs = require("fs");
223     const options = {
224       key: fs.readFileSync(__dirname + "/http2-cert.key"),
225       cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
226     };
227     const http2 = require("http2");
228     global.server = http2.createSecureServer(
229       options,
230       BaseNodeHTTPServerCode.globalHandler
231     );
233     global.sessionCount = 0;
234     global.server.on("session", () => {
235       global.sessionCount++;
236     });
238     let serverPort = await ADB.listenAndForwardPort(global.server, port);
239     return serverPort;
240   }
242   static sessionCount() {
243     return global.sessionCount;
244   }
247 class NodeHTTP2Server extends BaseNodeServer {
248   _protocol = "https";
249   _version = "h2";
250   /// Starts the server
251   /// @port - default 0
252   ///    when provided, will attempt to listen on that port.
253   async start(port = 0) {
254     this.processId = await NodeServer.fork();
256     await this.execute(BaseNodeHTTPServerCode);
257     await this.execute(NodeHTTP2ServerCode);
258     await this.execute(ADB);
259     this._port = await this.execute(`NodeHTTP2ServerCode.startServer(${port})`);
260     await this.execute(`global.path_handlers = {};`);
261   }
263   async sessionCount() {
264     let count = this.execute(`NodeHTTP2ServerCode.sessionCount()`);
265     return count;
266   }
269 // Base HTTP proxy
271 class BaseProxyCode {
272   static proxyHandler(req, res) {
273     if (req.url.startsWith("/")) {
274       res.writeHead(405);
275       res.end();
276       return;
277     }
279     let url = new URL(req.url);
280     const http = require("http");
281     let preq = http
282       .request(
283         {
284           method: req.method,
285           path: url.pathname,
286           port: url.port,
287           host: url.hostname,
288           protocol: url.protocol,
289         },
290         proxyresp => {
291           res.writeHead(
292             proxyresp.statusCode,
293             proxyresp.statusMessage,
294             proxyresp.headers
295           );
296           proxyresp.on("data", chunk => {
297             if (!res.writableEnded) {
298               res.write(chunk);
299             }
300           });
301           proxyresp.on("end", () => {
302             res.end();
303           });
304         }
305       )
306       .on("error", e => {
307         console.log(`sock err: ${e}`);
308       });
309     if (req.method != "POST") {
310       preq.end();
311     } else {
312       req.on("data", chunk => {
313         if (!preq.writableEnded) {
314           preq.write(chunk);
315         }
316       });
317       req.on("end", () => preq.end());
318     }
319   }
321   static onConnect(req, clientSocket, head) {
322     if (global.connect_handler) {
323       global.connect_handler(req, clientSocket, head);
324       return;
325     }
326     const net = require("net");
327     // Connect to an origin server
328     const { port, hostname } = new URL(`https://${req.url}`);
329     const serverSocket = net
330       .connect(
331         {
332           port: port || 443,
333           host: hostname,
334           family: 4, // Specifies to use IPv4
335         },
336         () => {
337           clientSocket.write(
338             "HTTP/1.1 200 Connection Established\r\n" +
339               "Proxy-agent: Node.js-Proxy\r\n" +
340               "\r\n"
341           );
342           serverSocket.write(head);
343           serverSocket.pipe(clientSocket);
344           clientSocket.pipe(serverSocket);
345         }
346       )
347       .on("error", e => {
348         console.log("error" + e);
349         // The socket will error out when we kill the connection
350         // just ignore it.
351       });
353     clientSocket.on("error", e => {
354       console.log("client error" + e);
355       // Sometimes we got ECONNRESET error on windows platform.
356       // Ignore it for now.
357     });
358   }
361 class BaseHTTPProxy extends BaseNodeServer {
362   registerFilter() {
363     const pps =
364       Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
365     this.filter = new NodeProxyFilter(
366       this.protocol(),
367       "localhost",
368       this.port(),
369       0
370     );
371     pps.registerFilter(this.filter, 10);
372     registerCleanupFunction(() => {
373       this.unregisterFilter();
374     });
375   }
377   unregisterFilter() {
378     const pps =
379       Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
380     if (this.filter) {
381       pps.unregisterFilter(this.filter);
382       this.filter = undefined;
383     }
384   }
386   /// Stops the server
387   async stop() {
388     this.unregisterFilter();
389     await super.stop();
390   }
392   async registerConnectHandler(handler) {
393     return this.execute(`global.connect_handler = ${handler.toString()}`);
394   }
397 // HTTP1 Proxy
399 class NodeProxyFilter {
400   constructor(type, host, port, flags) {
401     this._type = type;
402     this._host = host;
403     this._port = port;
404     this._flags = flags;
405     this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]);
406   }
407   applyFilter(uri, pi, cb) {
408     const pps =
409       Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
410     cb.onProxyFilterResult(
411       pps.newProxyInfo(
412         this._type,
413         this._host,
414         this._port,
415         "",
416         "",
417         this._flags,
418         1000,
419         null
420       )
421     );
422   }
425 class HTTPProxyCode {
426   static async startServer(port) {
427     const http = require("http");
428     global.proxy = http.createServer(BaseProxyCode.proxyHandler);
429     global.proxy.on("connect", BaseProxyCode.onConnect);
431     let proxyPort = await ADB.listenAndForwardPort(global.proxy, port);
432     return proxyPort;
433   }
436 class NodeHTTPProxyServer extends BaseHTTPProxy {
437   _protocol = "http";
438   /// Starts the server
439   /// @port - default 0
440   ///    when provided, will attempt to listen on that port.
441   async start(port = 0) {
442     this.processId = await NodeServer.fork();
444     await this.execute(BaseProxyCode);
445     await this.execute(HTTPProxyCode);
446     await this.execute(ADB);
447     await this.execute(`global.connect_handler = null;`);
448     this._port = await this.execute(`HTTPProxyCode.startServer(${port})`);
450     this.registerFilter();
451   }
454 // HTTPS proxy
456 class HTTPSProxyCode {
457   static async startServer(port) {
458     const fs = require("fs");
459     const options = {
460       key: fs.readFileSync(__dirname + "/proxy-cert.key"),
461       cert: fs.readFileSync(__dirname + "/proxy-cert.pem"),
462     };
463     const https = require("https");
464     global.proxy = https.createServer(options, BaseProxyCode.proxyHandler);
465     global.proxy.on("connect", BaseProxyCode.onConnect);
467     let proxyPort = await ADB.listenAndForwardPort(global.proxy, port);
468     return proxyPort;
469   }
472 class NodeHTTPSProxyServer extends BaseHTTPProxy {
473   _protocol = "https";
474   /// Starts the server
475   /// @port - default 0
476   ///    when provided, will attempt to listen on that port.
477   async start(port = 0) {
478     this.processId = await NodeServer.fork();
480     await this.execute(BaseProxyCode);
481     await this.execute(HTTPSProxyCode);
482     await this.execute(ADB);
483     await this.execute(`global.connect_handler = null;`);
484     this._port = await this.execute(`HTTPSProxyCode.startServer(${port})`);
486     this.registerFilter();
487   }
490 // HTTP2 proxy
492 class HTTP2ProxyCode {
493   static async startServer(port, auth) {
494     const fs = require("fs");
495     const options = {
496       key: fs.readFileSync(__dirname + "/proxy-cert.key"),
497       cert: fs.readFileSync(__dirname + "/proxy-cert.pem"),
498     };
499     const http2 = require("http2");
500     global.proxy = http2.createSecureServer(options);
501     global.socketCounts = {};
502     this.setupProxy(auth);
504     let proxyPort = await ADB.listenAndForwardPort(global.proxy, port);
505     return proxyPort;
506   }
508   static setupProxy(auth) {
509     if (!global.proxy) {
510       throw new Error("proxy is null");
511     }
513     global.proxy.on("stream", (stream, headers) => {
514       if (headers[":scheme"] === "http") {
515         const http = require("http");
516         let url = new URL(
517           `${headers[":scheme"]}://${headers[":authority"]}${headers[":path"]}`
518         );
519         let req = http
520           .request(
521             {
522               method: headers[":method"],
523               path: headers[":path"],
524               port: url.port,
525               host: url.hostname,
526               protocol: url.protocol,
527             },
528             proxyresp => {
529               let proxyheaders = Object.assign({}, proxyresp.headers);
530               // Filter out some prohibited headers.
531               ["connection", "transfer-encoding", "keep-alive"].forEach(
532                 prop => {
533                   delete proxyheaders[prop];
534                 }
535               );
536               try {
537                 stream.respond(
538                   Object.assign(
539                     { ":status": proxyresp.statusCode },
540                     proxyheaders
541                   )
542                 );
543               } catch (e) {
544                 // The channel may have been closed already.
545                 if (e.message != "The stream has been destroyed") {
546                   throw e;
547                 }
548               }
549               proxyresp.on("data", chunk => {
550                 if (stream.writable) {
551                   stream.write(chunk);
552                 }
553               });
554               proxyresp.on("end", () => {
555                 stream.end();
556               });
557             }
558           )
559           .on("error", e => {
560             console.log(`sock err: ${e}`);
561           });
563         if (headers[":method"] != "POST") {
564           req.end();
565         } else {
566           stream.on("data", chunk => {
567             if (!req.writableEnded) {
568               req.write(chunk);
569             }
570           });
571           stream.on("end", () => req.end());
572         }
573         return;
574       }
575       if (headers[":method"] !== "CONNECT") {
576         // Only accept CONNECT requests
577         stream.respond({ ":status": 405 });
578         stream.end();
579         return;
580       }
582       const authorization_token = headers["proxy-authorization"];
583       if (auth && !authorization_token) {
584         stream.respond({
585           ":status": 407,
586           "proxy-authenticate": "Basic realm='foo'",
587         });
588         stream.end();
589         return;
590       }
592       const target = headers[":authority"];
593       const { port } = new URL(`https://${target}`);
594       const net = require("net");
595       const socket = net.connect(port, "127.0.0.1", () => {
596         try {
597           global.socketCounts[socket.remotePort] =
598             (global.socketCounts[socket.remotePort] || 0) + 1;
599           stream.respond({ ":status": 200 });
600           socket.pipe(stream);
601           stream.pipe(socket);
602         } catch (exception) {
603           console.log(exception);
604           stream.close();
605         }
606       });
607       const http2 = require("http2");
608       socket.on("error", error => {
609         const status = error.errno == "ENOTFOUND" ? 404 : 502;
610         try {
611           // If we already sent headers when the socket connected
612           // then sending the status again would throw.
613           if (!stream.sentHeaders) {
614             stream.respond({ ":status": status });
615           }
616           stream.end();
617         } catch (exception) {
618           stream.close(http2.constants.NGHTTP2_CONNECT_ERROR);
619         }
620       });
621       stream.on("close", () => {
622         socket.end();
623       });
624       socket.on("close", () => {
625         stream.close();
626       });
627       stream.on("end", () => {
628         socket.end();
629       });
630       stream.on("aborted", () => {
631         socket.end();
632       });
633       stream.on("error", error => {
634         console.log("RESPONSE STREAM ERROR", error);
635       });
636     });
637   }
639   static socketCount(port) {
640     return global.socketCounts[port];
641   }
644 class NodeHTTP2ProxyServer extends BaseHTTPProxy {
645   _protocol = "https";
646   /// Starts the server
647   /// @port - default 0
648   ///    when provided, will attempt to listen on that port.
649   async start(port = 0, auth) {
650     this.processId = await NodeServer.fork();
652     await this.execute(BaseProxyCode);
653     await this.execute(HTTP2ProxyCode);
654     await this.execute(ADB);
655     await this.execute(`global.connect_handler = null;`);
656     this._port = await this.execute(
657       `HTTP2ProxyCode.startServer(${port}, ${auth})`
658     );
660     this.registerFilter();
661   }
663   async socketCount(port) {
664     let count = this.execute(`HTTP2ProxyCode.socketCount(${port})`);
665     return count;
666   }
669 // websocket server
671 class NodeWebSocketServerCode extends BaseNodeHTTPServerCode {
672   static messageHandler(data, ws) {
673     if (global.wsInputHandler) {
674       global.wsInputHandler(data, ws);
675       return;
676     }
678     ws.send("test");
679   }
681   static async startServer(port) {
682     const fs = require("fs");
683     const options = {
684       key: fs.readFileSync(__dirname + "/http2-cert.key"),
685       cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
686     };
687     const https = require("https");
688     global.server = https.createServer(
689       options,
690       BaseNodeHTTPServerCode.globalHandler
691     );
693     let node_ws_root = `${__dirname}/../node-ws`;
694     const WebSocket = require(`${node_ws_root}/lib/websocket`);
695     WebSocket.Server = require(`${node_ws_root}/lib/websocket-server`);
696     global.webSocketServer = new WebSocket.Server({ server: global.server });
697     global.webSocketServer.on("connection", function connection(ws) {
698       ws.on("message", data =>
699         NodeWebSocketServerCode.messageHandler(data, ws)
700       );
701     });
703     let serverPort = await ADB.listenAndForwardPort(global.server, port);
704     return serverPort;
705   }
708 class NodeWebSocketServer extends BaseNodeServer {
709   _protocol = "wss";
710   /// Starts the server
711   /// @port - default 0
712   ///    when provided, will attempt to listen on that port.
713   async start(port = 0) {
714     this.processId = await NodeServer.fork();
716     await this.execute(BaseNodeHTTPServerCode);
717     await this.execute(NodeWebSocketServerCode);
718     await this.execute(ADB);
719     this._port = await this.execute(
720       `NodeWebSocketServerCode.startServer(${port})`
721     );
722     await this.execute(`global.path_handlers = {};`);
723     await this.execute(`global.wsInputHandler = null;`);
724   }
726   async registerMessageHandler(handler) {
727     return this.execute(`global.wsInputHandler = ${handler.toString()}`);
728   }
731 // websocket http2 server
732 // This code is inspired by
733 // https://github.com/szmarczak/http2-wrapper/blob/master/examples/ws/server.js
734 class NodeWebSocketHttp2ServerCode extends BaseNodeHTTPServerCode {
735   static async startServer(port) {
736     const fs = require("fs");
737     const options = {
738       key: fs.readFileSync(__dirname + "/http2-cert.key"),
739       cert: fs.readFileSync(__dirname + "/http2-cert.pem"),
740       settings: {
741         enableConnectProtocol: true,
742       },
743     };
744     const http2 = require("http2");
745     global.h2Server = http2.createSecureServer(options);
747     let node_ws_root = `${__dirname}/../node-ws`;
748     const WebSocket = require(`${node_ws_root}/lib/websocket`);
750     global.h2Server.on("stream", (stream, headers) => {
751       if (headers[":method"] === "CONNECT") {
752         stream.respond();
754         const ws = new WebSocket(null);
755         stream.setNoDelay = () => {};
756         ws.setSocket(stream, Buffer.from(""), 100 * 1024 * 1024);
758         ws.on("message", data => {
759           if (global.wsInputHandler) {
760             global.wsInputHandler(data, ws);
761             return;
762           }
764           ws.send("test");
765         });
766       } else {
767         stream.respond();
768         stream.end("ok");
769       }
770     });
772     let serverPort = await ADB.listenAndForwardPort(global.h2Server, port);
773     return serverPort;
774   }
777 class NodeWebSocketHttp2Server extends BaseNodeServer {
778   _protocol = "h2ws";
779   /// Starts the server
780   /// @port - default 0
781   ///    when provided, will attempt to listen on that port.
782   async start(port = 0) {
783     this.processId = await NodeServer.fork();
785     await this.execute(BaseNodeHTTPServerCode);
786     await this.execute(NodeWebSocketHttp2ServerCode);
787     await this.execute(ADB);
788     this._port = await this.execute(
789       `NodeWebSocketHttp2ServerCode.startServer(${port})`
790     );
791     await this.execute(`global.path_handlers = {};`);
792     await this.execute(`global.wsInputHandler = null;`);
793   }
795   async registerMessageHandler(handler) {
796     return this.execute(`global.wsInputHandler = ${handler.toString()}`);
797   }
800 // Helper functions
802 async function with_node_servers(arrayOfClasses, asyncClosure) {
803   for (let s of arrayOfClasses) {
804     let server = new s();
805     await server.start();
806     registerCleanupFunction(async () => {
807       await server.stop();
808     });
809     await asyncClosure(server);
810     await server.stop();
811   }
814 // nsITLSServerSocket needs a certificate with a corresponding private key
815 // available. xpcshell tests can import the test file "client-cert.p12" using
816 // the password "password", resulting in a certificate with the common name
817 // "Test End-entity" being available with a corresponding private key.
818 function getTestServerCertificate() {
819   const certDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
820     Ci.nsIX509CertDB
821   );
822   const certFile = do_get_file("client-cert.p12");
823   certDB.importPKCS12File(certFile, "password");
824   for (const cert of certDB.getCerts()) {
825     if (cert.commonName == "Test End-entity") {
826       return cert;
827     }
828   }
829   return null;
832 class WebSocketConnection {
833   constructor() {
834     this._openPromise = new Promise(resolve => {
835       this._openCallback = resolve;
836     });
838     this._stopPromise = new Promise(resolve => {
839       this._stopCallback = resolve;
840     });
842     this._msgPromise = new Promise(resolve => {
843       this._msgCallback = resolve;
844     });
846     this._proxyAvailablePromise = new Promise(resolve => {
847       this._proxyAvailCallback = resolve;
848     });
850     this._messages = [];
851     this._ws = null;
852   }
854   get QueryInterface() {
855     return ChromeUtils.generateQI([
856       "nsIWebSocketListener",
857       "nsIProtocolProxyCallback",
858     ]);
859   }
861   onAcknowledge() {}
862   onBinaryMessageAvailable(aContext, aMsg) {
863     this._messages.push(aMsg);
864     this._msgCallback();
865   }
866   onMessageAvailable() {}
867   onServerClose() {}
868   onWebSocketListenerStart() {}
869   onStart() {
870     this._openCallback();
871   }
872   onStop(aContext, aStatusCode) {
873     this._stopCallback({ status: aStatusCode });
874     this._ws = null;
875   }
876   onProxyAvailable(req, chan, proxyInfo) {
877     if (proxyInfo) {
878       this._proxyAvailCallback({ type: proxyInfo.type });
879     } else {
880       this._proxyAvailCallback({});
881     }
882   }
884   static makeWebSocketChan() {
885     let chan = Cc["@mozilla.org/network/protocol;1?name=wss"].createInstance(
886       Ci.nsIWebSocketChannel
887     );
888     chan.initLoadInfo(
889       null, // aLoadingNode
890       Services.scriptSecurityManager.getSystemPrincipal(),
891       null, // aTriggeringPrincipal
892       Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
893       Ci.nsIContentPolicy.TYPE_WEBSOCKET
894     );
895     return chan;
896   }
897   // Returns a promise that resolves when the websocket channel is opened.
898   open(url) {
899     this._ws = WebSocketConnection.makeWebSocketChan();
900     let uri = Services.io.newURI(url);
901     this._ws.asyncOpen(uri, url, {}, 0, this, null);
902     return this._openPromise;
903   }
904   // Closes the inner websocket. code and reason arguments are optional.
905   close(code, reason) {
906     this._ws.close(code || Ci.nsIWebSocketChannel.CLOSE_NORMAL, reason || "");
907   }
908   // Sends a message to the server.
909   send(msg) {
910     this._ws.sendMsg(msg);
911   }
912   // Returns a promise that resolves when the channel's onStop is called.
913   // Promise resolves with an `{status}` object, where status is the
914   // result passed to onStop.
915   finished() {
916     return this._stopPromise;
917   }
918   getProxyInfo() {
919     return this._proxyAvailablePromise;
920   }
922   // Returned promise resolves with an array of received messages
923   // If messages have been received in the the past before calling
924   // receiveMessages, the promise will immediately resolve. Otherwise
925   // it will resolve when the first message is received.
926   async receiveMessages() {
927     await this._msgPromise;
928     this._msgPromise = new Promise(resolve => {
929       this._msgCallback = resolve;
930     });
931     let messages = this._messages;
932     this._messages = [];
933     return messages;
934   }