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/. */
7 ChromeUtils.defineESModuleGetters(lazy, {
8 Log: "chrome://remote/content/shared/Log.sys.mjs",
9 HTTP_404: "chrome://remote/content/server/httpd.sys.mjs",
10 HTTP_405: "chrome://remote/content/server/httpd.sys.mjs",
11 HTTP_500: "chrome://remote/content/server/httpd.sys.mjs",
12 Protocol: "chrome://remote/content/cdp/Protocol.sys.mjs",
13 RemoteAgentError: "chrome://remote/content/cdp/Error.sys.mjs",
14 TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
17 export class JSONHandler {
22 handler: this.getVersion.bind(this),
26 handler: this.getProtocol.bind(this),
30 handler: this.getTargetList.bind(this),
34 handler: this.getTargetList.bind(this),
37 // PUT only - /json/new?{url}
39 handler: this.newTarget.bind(this),
43 // /json/activate/{targetId}
45 handler: this.activateTarget.bind(this),
49 // /json/close/{targetId}
51 handler: this.closeTarget.bind(this),
58 const mainProcessTarget = this.cdp.targetList.getMainProcessTarget();
60 const { userAgent } = Cc[
61 "@mozilla.org/network/protocol;1?name=http"
62 ].getService(Ci.nsIHttpProtocolHandler);
66 Browser: `${Services.appinfo.name}/${Services.appinfo.version}`,
67 "Protocol-Version": "1.3",
68 "User-Agent": userAgent,
70 "WebKit-Version": "1.0",
71 webSocketDebuggerUrl: mainProcessTarget.toJSON().webSocketDebuggerUrl,
77 return { body: lazy.Protocol.Description };
81 return { body: [...this.cdp.targetList].filter(x => x.type !== "browser") };
84 /** HTTP copy of Target.createTarget() */
85 async newTarget(url) {
86 const onTarget = this.cdp.targetList.once("target-created");
89 const tab = await lazy.TabManager.addTab({
93 // Get the newly created target
94 const target = await onTarget;
95 if (tab.linkedBrowser != target.browser) {
97 "Unexpected tab opened: " + tab.linkedBrowser.currentURI.spec
101 const returnJson = target.toJSON();
103 // Load URL if given, otherwise stay on about:blank
107 validURL = Services.io.newURI(url);
109 // If we failed to parse given URL, return now since we already loaded about:blank
110 return { body: returnJson };
113 target.browsingContext.loadURI(validURL, {
115 Services.scriptSecurityManager.getSystemPrincipal(),
118 // Force the URL in the returned target JSON to match given
119 // even if loading/will fail (matches Chromium behavior)
120 returnJson.url = url;
123 return { body: returnJson };
126 /** HTTP copy of Target.activateTarget() */
127 async activateTarget(targetId) {
128 // Try to get the target from given id
129 const target = this.cdp.targetList.getById(targetId);
133 status: lazy.HTTP_404,
134 body: `No such target id: ${targetId}`,
139 // Select the tab (this endpoint does not show the window)
140 await lazy.TabManager.selectTab(target.tab);
142 return { body: "Target activated", json: false };
145 /** HTTP copy of Target.closeTarget() */
146 async closeTarget(targetId) {
147 // Try to get the target from given id
148 const target = this.cdp.targetList.getById(targetId);
152 status: lazy.HTTP_404,
153 body: `No such target id: ${targetId}`,
159 await lazy.TabManager.removeTab(target.tab);
161 return { body: "Target is closing", json: false };
164 // nsIHttpRequestHandler
166 async handle(request, response) {
167 // Mark request as async so we can execute async routes and return values
168 response.processAsync();
170 // Run a provided route (function) with an argument
171 const runRoute = async (route, data) => {
173 // Run the route to get data to return
175 status = { code: 200, description: "OK" },
178 } = await route(data);
180 // Stringify into returnable JSON if wanted
182 ? JSON.stringify(body, null, lazy.Log.verbose ? "\t" : null)
185 // Handle HTTP response
186 response.setStatusLine(
191 response.setHeader("Content-Type", "application/json");
192 response.setHeader("Content-Security-Policy", "frame-ancestors 'none'");
193 response.write(payload);
195 new lazy.RemoteAgentError(e).notify();
197 // Mark as 500 as an error has occured internally
198 response.setStatusLine(
201 lazy.HTTP_500.description
206 // Trim trailing slashes to conform with expected routes
207 const path = request.path.replace(/\/+$/, "");
210 for (const _route in this.routes) {
211 // Prefixed/parameter route (/path/{parameter})
212 if (path.startsWith(_route + "/") && this.routes[_route].parameter) {
217 // Regular route (/path/example)
218 if (path === _route) {
225 // Route does not exist
226 response.setStatusLine(
229 lazy.HTTP_404.description
231 response.write("Unknown command: " + path.replace("/json/", ""));
233 return response.finish();
236 const { handler, method, parameter } = this.routes[route];
238 // If only one valid method for route, check method matches
239 if (method && request.method !== method) {
240 response.setStatusLine(
243 lazy.HTTP_405.description
246 `Using unsafe HTTP verb ${request.method} to invoke ${route}. This action supports only PUT verb.`
248 return response.finish();
252 await runRoute(handler, path.split("/").pop());
254 await runRoute(handler, request.queryString);
258 return response.finish();
263 get QueryInterface() {
264 return ChromeUtils.generateQI(["nsIHttpRequestHandler"]);