Bug 1551011 [wpt PR 16697] - [XHR] Use response tainting to calculate CORS-exposed...
[gecko.git] / remote / RemoteAgent.jsm
blob3936354723eb648115bc5a562d04ab10a809a40c
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 var EXPORTED_SYMBOLS = ["RemoteAgent"];
9 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
10 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
12 XPCOMUtils.defineLazyModuleGetters(this, {
13   FatalError: "chrome://remote/content/Error.jsm",
14   HttpServer: "chrome://remote/content/server/HTTPD.jsm",
15   JSONHandler: "chrome://remote/content/JSONHandler.jsm",
16   Log: "chrome://remote/content/Log.jsm",
17   NetUtil: "resource://gre/modules/NetUtil.jsm",
18   Observer: "chrome://remote/content/Observer.jsm",
19   Preferences: "resource://gre/modules/Preferences.jsm",
20   RecommendedPreferences: "chrome://remote/content/RecommendedPreferences.jsm",
21   TabObserver: "chrome://remote/content/WindowManager.jsm",
22   Targets: "chrome://remote/content/targets/Targets.jsm",
23 });
24 XPCOMUtils.defineLazyGetter(this, "log", Log.get);
26 const ENABLED = "remote.enabled";
27 const FORCE_LOCAL = "remote.force-local";
29 const DEFAULT_HOST = "localhost";
30 const DEFAULT_PORT = 9222;
31 const LOOPBACKS = ["localhost", "127.0.0.1", "[::1]"];
33 class RemoteAgentClass {
34   init() {
35     if (!Preferences.get(ENABLED, false)) {
36       throw new Error("Remote agent is disabled by its preference");
37     }
38     if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
39       throw new Error("Remote agent can only be instantiated from the parent process");
40     }
42     if (this.server) {
43       return;
44     }
46     this.server = new HttpServer();
47     this.targets = new Targets();
49     this.server.registerPrefixHandler("/json/", new JSONHandler(this));
51     this.tabs = new TabObserver({registerExisting: true});
52     this.tabs.on("open", (eventName, tab) => {
53       this.targets.connect(tab.linkedBrowser);
54     });
55     this.tabs.on("close", (eventName, tab) => {
56       this.targets.disconnect(tab.linkedBrowser);
57     });
59     this.targets.on("connect", (eventName, target) => {
60       if (!target.path) {
61         throw new Error(`Target is missing 'path' attribute: ${target}`);
62       }
63       this.server.registerPathHandler(target.path, target);
64     });
65     this.targets.on("disconnect", (eventName, target) => {
66       // TODO: This removes the entry added by registerPathHandler, should rather expose
67       // an unregisterPathHandler method on nsHttpServer.
68       delete this.server._handler._overridePaths[target.path];
69     });
70   }
72   get listening() {
73     return !!this.server && !this.server._socketClosed;
74   }
76   async listen(address) {
77     if (!(address instanceof Ci.nsIURI)) {
78       throw new TypeError(`Expected nsIURI: ${address}`);
79     }
81     let {host, port} = address;
82     if (Preferences.get(FORCE_LOCAL) && !LOOPBACKS.includes(host)) {
83       throw new Error("Restricted to loopback devices");
84     }
86     // nsIServerSocket uses -1 for atomic port allocation
87     if (port === 0) {
88       port = -1;
89     }
91     if (this.listening) {
92       return;
93     }
95     this.init();
97     await this.tabs.start();
99     try {
100       // Immediatly instantiate the main process target in order
101       // to be accessible via HTTP endpoint on startup
102       const mainTarget = this.targets.getMainProcessTarget();
104       this.server._start(port, host);
105       dump(`DevTools listening on ${mainTarget.wsDebuggerURL}\n`);
106     } catch (e) {
107       throw new Error(`Unable to start remote agent: ${e.message}`, e);
108     }
110     Preferences.set(RecommendedPreferences);
111   }
113   async close() {
114     if (this.listening) {
115       try {
116         // Disconnect the targets first in order to ensure closing all pending
117         // connection first. Otherwise Httpd's stop is not going to resolve.
118         this.targets.clear();
120         await this.server.stop();
122         Preferences.reset(Object.keys(RecommendedPreferences));
124         this.tabs.stop();
125       } catch (e) {
126         throw new Error(`Unable to stop agent: ${e.message}`, e);
127       }
129       log.info("Stopped listening");
130     }
131   }
133   get scheme() {
134     if (!this.server) {
135       return null;
136     }
137     return this.server.identity.primaryScheme;
138   }
140   get host() {
141     if (!this.server) {
142       return null;
143     }
144     return this.server.identity.primaryHost;
145   }
147   get port() {
148     if (!this.server) {
149       return null;
150     }
151     return this.server.identity.primaryPort;
152   }
154   // nsICommandLineHandler
156   async handle(cmdLine) {
157     function flag(name) {
158       const caseSensitive = true;
159       try {
160         return cmdLine.handleFlagWithParam(name, caseSensitive);
161       } catch (e) {
162         return cmdLine.handleFlag(name, caseSensitive);
163       }
164     }
166     const remoteDebugger = flag("remote-debugger");
167     const remoteDebuggingPort = flag("remote-debugging-port");
169     if (remoteDebugger && remoteDebuggingPort) {
170       log.fatal("Conflicting flags --remote-debugger and --remote-debugging-port");
171       cmdLine.preventDefault = true;
172       return;
173     }
175     if (!remoteDebugger && !remoteDebuggingPort) {
176       return;
177     }
179     let host, port;
180     if (typeof remoteDebugger == "string") {
181       [host, port] = remoteDebugger.split(":");
182     } else if (typeof remoteDebuggingPort == "string") {
183       port = remoteDebuggingPort;
184     }
186     let addr;
187     try {
188       addr = NetUtil.newURI(`http://${host || DEFAULT_HOST}:${port || DEFAULT_PORT}/`);
189     } catch (e) {
190       log.fatal(`Expected address syntax [<host>]:<port>: ${remoteDebugger || remoteDebuggingPort}`);
191       cmdLine.preventDefault = true;
192       return;
193     }
195     this.init();
197     await Observer.once("sessionstore-windows-restored");
199     try {
200       this.listen(addr);
201     } catch (e) {
202       this.close();
203       throw new FatalError(`Unable to start remote agent on ${addr.spec}: ${e.message}`, e);
204      }
205   }
207   get helpInfo() {
208     return "  --remote-debugger [<host>][:<port>]\n" +
209            "  --remote-debugging-port <port> Start the Firefox remote agent, which is \n" +
210            "                     a low-level debugging interface based on the CDP protocol.\n" +
211            "                     Defaults to listen on localhost:9222.\n";
212   }
214   // XPCOM
216   get QueryInterface() {
217     return ChromeUtils.generateQI([Ci.nsICommandLineHandler]);
218   }
221 var RemoteAgent = new RemoteAgentClass();