Bug 1685822 [wpt PR 27117] - Update wpt metadata, a=testonly
[gecko.git] / remote / RemoteAgent.jsm
blobe06067bfac22a6fe739f7ca0af6352c59b13e05b
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", "RemoteAgentFactory"];
9 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
10 const { XPCOMUtils } = ChromeUtils.import(
11   "resource://gre/modules/XPCOMUtils.jsm"
14 XPCOMUtils.defineLazyModuleGetters(this, {
15   HttpServer: "chrome://remote/content/server/HTTPD.jsm",
16   JSONHandler: "chrome://remote/content/JSONHandler.jsm",
17   Log: "chrome://remote/content/Log.jsm",
18   Preferences: "resource://gre/modules/Preferences.jsm",
19   RecommendedPreferences: "chrome://remote/content/RecommendedPreferences.jsm",
20   TargetList: "chrome://remote/content/targets/TargetList.jsm",
21 });
22 XPCOMUtils.defineLazyGetter(this, "log", Log.get);
24 const ENABLED = "remote.enabled";
25 const FORCE_LOCAL = "remote.force-local";
27 const LOOPBACKS = ["localhost", "127.0.0.1", "[::1]"];
29 class RemoteAgentClass {
30   get listening() {
31     return !!this.server && !this.server.isStopped();
32   }
34   get debuggerAddress() {
35     if (!this.server) {
36       return "";
37     }
39     return `${this.host}:${this.port}`;
40   }
42   listen(url) {
43     if (!Preferences.get(ENABLED, false)) {
44       throw Components.Exception(
45         "Disabled by preference",
46         Cr.NS_ERROR_NOT_AVAILABLE
47       );
48     }
49     if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
50       throw Components.Exception(
51         "May only be instantiated in parent process",
52         Cr.NS_ERROR_LAUNCHED_CHILD_PROCESS
53       );
54     }
56     if (this.listening) {
57       return Promise.resolve();
58     }
60     if (!(url instanceof Ci.nsIURI)) {
61       url = Services.io.newURI(url);
62     }
64     let { host, port } = url;
65     if (Preferences.get(FORCE_LOCAL) && !LOOPBACKS.includes(host)) {
66       throw Components.Exception(
67         "Restricted to loopback devices",
68         Cr.NS_ERROR_ILLEGAL_VALUE
69       );
70     }
72     // nsIServerSocket uses -1 for atomic port allocation
73     if (port === 0) {
74       port = -1;
75     }
77     Preferences.set(RecommendedPreferences);
79     this.server = new HttpServer();
80     this.server.registerPrefixHandler("/json/", new JSONHandler(this));
82     this.targets = new TargetList();
83     this.targets.on("target-created", (eventName, target) => {
84       this.server.registerPathHandler(target.path, target);
85     });
86     this.targets.on("target-destroyed", (eventName, target) => {
87       this.server.registerPathHandler(target.path, null);
88     });
90     return this.asyncListen(host, port);
91   }
93   async asyncListen(host, port) {
94     try {
95       await this.targets.watchForTargets();
97       // Immediatly instantiate the main process target in order
98       // to be accessible via HTTP endpoint on startup
99       const mainTarget = this.targets.getMainProcessTarget();
101       this.server._start(port, host);
102       Services.obs.notifyObservers(
103         null,
104         "remote-listening",
105         mainTarget.wsDebuggerURL
106       );
107     } catch (e) {
108       await this.close();
109       log.error(`Unable to start remote agent: ${e.message}`, e);
110     }
111   }
113   close() {
114     try {
115       // if called early at startup, preferences may not be available
116       try {
117         Preferences.reset(Object.keys(RecommendedPreferences));
118       } catch (e) {}
120       // destroy targets before stopping server,
121       // otherwise the HTTP will fail to stop
122       if (this.targets) {
123         this.targets.destructor();
124       }
126       if (this.listening) {
127         return this.server.stop();
128       }
129     } catch (e) {
130       // this function must never fail
131       log.error("unable to stop listener", e);
132     } finally {
133       this.server = null;
134       this.targets = null;
135     }
137     return Promise.resolve();
138   }
140   get scheme() {
141     if (!this.server) {
142       return null;
143     }
144     return this.server.identity.primaryScheme;
145   }
147   get host() {
148     if (!this.server) {
149       return null;
150     }
152     // Bug 1675471: When using the nsIRemoteAgent interface the HTTPd server's
153     // primary identity ("this.server.identity.primaryHost") is lazily set.
154     return this.server._host;
155   }
157   get port() {
158     if (!this.server) {
159       return null;
160     }
162     // Bug 1675471: When using the nsIRemoteAgent interface the HTTPd server's
163     // primary identity ("this.server.identity.primaryPort") is lazily set.
164     return this.server._port;
165   }
167   // XPCOM
169   get QueryInterface() {
170     return ChromeUtils.generateQI(["nsIRemoteAgent"]);
171   }
174 var RemoteAgent = new RemoteAgentClass();
176 // This is used by the XPCOM codepath which expects a constructor
177 var RemoteAgentFactory = function() {
178   return RemoteAgent;