summaryrefslogtreecommitdiffstats
path: root/remote/RemoteAgent.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'remote/RemoteAgent.jsm')
-rw-r--r--remote/RemoteAgent.jsm179
1 files changed, 179 insertions, 0 deletions
diff --git a/remote/RemoteAgent.jsm b/remote/RemoteAgent.jsm
new file mode 100644
index 0000000000..e06067bfac
--- /dev/null
+++ b/remote/RemoteAgent.jsm
@@ -0,0 +1,179 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["RemoteAgent", "RemoteAgentFactory"];
+
+const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { XPCOMUtils } = ChromeUtils.import(
+ "resource://gre/modules/XPCOMUtils.jsm"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ HttpServer: "chrome://remote/content/server/HTTPD.jsm",
+ JSONHandler: "chrome://remote/content/JSONHandler.jsm",
+ Log: "chrome://remote/content/Log.jsm",
+ Preferences: "resource://gre/modules/Preferences.jsm",
+ RecommendedPreferences: "chrome://remote/content/RecommendedPreferences.jsm",
+ TargetList: "chrome://remote/content/targets/TargetList.jsm",
+});
+XPCOMUtils.defineLazyGetter(this, "log", Log.get);
+
+const ENABLED = "remote.enabled";
+const FORCE_LOCAL = "remote.force-local";
+
+const LOOPBACKS = ["localhost", "127.0.0.1", "[::1]"];
+
+class RemoteAgentClass {
+ get listening() {
+ return !!this.server && !this.server.isStopped();
+ }
+
+ get debuggerAddress() {
+ if (!this.server) {
+ return "";
+ }
+
+ return `${this.host}:${this.port}`;
+ }
+
+ listen(url) {
+ if (!Preferences.get(ENABLED, false)) {
+ throw Components.Exception(
+ "Disabled by preference",
+ Cr.NS_ERROR_NOT_AVAILABLE
+ );
+ }
+ if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+ throw Components.Exception(
+ "May only be instantiated in parent process",
+ Cr.NS_ERROR_LAUNCHED_CHILD_PROCESS
+ );
+ }
+
+ if (this.listening) {
+ return Promise.resolve();
+ }
+
+ if (!(url instanceof Ci.nsIURI)) {
+ url = Services.io.newURI(url);
+ }
+
+ let { host, port } = url;
+ if (Preferences.get(FORCE_LOCAL) && !LOOPBACKS.includes(host)) {
+ throw Components.Exception(
+ "Restricted to loopback devices",
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ }
+
+ // nsIServerSocket uses -1 for atomic port allocation
+ if (port === 0) {
+ port = -1;
+ }
+
+ Preferences.set(RecommendedPreferences);
+
+ this.server = new HttpServer();
+ this.server.registerPrefixHandler("/json/", new JSONHandler(this));
+
+ this.targets = new TargetList();
+ this.targets.on("target-created", (eventName, target) => {
+ this.server.registerPathHandler(target.path, target);
+ });
+ this.targets.on("target-destroyed", (eventName, target) => {
+ this.server.registerPathHandler(target.path, null);
+ });
+
+ return this.asyncListen(host, port);
+ }
+
+ async asyncListen(host, port) {
+ try {
+ await this.targets.watchForTargets();
+
+ // Immediatly instantiate the main process target in order
+ // to be accessible via HTTP endpoint on startup
+ const mainTarget = this.targets.getMainProcessTarget();
+
+ this.server._start(port, host);
+ Services.obs.notifyObservers(
+ null,
+ "remote-listening",
+ mainTarget.wsDebuggerURL
+ );
+ } catch (e) {
+ await this.close();
+ log.error(`Unable to start remote agent: ${e.message}`, e);
+ }
+ }
+
+ close() {
+ try {
+ // if called early at startup, preferences may not be available
+ try {
+ Preferences.reset(Object.keys(RecommendedPreferences));
+ } catch (e) {}
+
+ // destroy targets before stopping server,
+ // otherwise the HTTP will fail to stop
+ if (this.targets) {
+ this.targets.destructor();
+ }
+
+ if (this.listening) {
+ return this.server.stop();
+ }
+ } catch (e) {
+ // this function must never fail
+ log.error("unable to stop listener", e);
+ } finally {
+ this.server = null;
+ this.targets = null;
+ }
+
+ return Promise.resolve();
+ }
+
+ get scheme() {
+ if (!this.server) {
+ return null;
+ }
+ return this.server.identity.primaryScheme;
+ }
+
+ get host() {
+ if (!this.server) {
+ return null;
+ }
+
+ // Bug 1675471: When using the nsIRemoteAgent interface the HTTPd server's
+ // primary identity ("this.server.identity.primaryHost") is lazily set.
+ return this.server._host;
+ }
+
+ get port() {
+ if (!this.server) {
+ return null;
+ }
+
+ // Bug 1675471: When using the nsIRemoteAgent interface the HTTPd server's
+ // primary identity ("this.server.identity.primaryPort") is lazily set.
+ return this.server._port;
+ }
+
+ // XPCOM
+
+ get QueryInterface() {
+ return ChromeUtils.generateQI(["nsIRemoteAgent"]);
+ }
+}
+
+var RemoteAgent = new RemoteAgentClass();
+
+// This is used by the XPCOM codepath which expects a constructor
+var RemoteAgentFactory = function() {
+ return RemoteAgent;
+};