mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-11 16:32:59 +00:00
b63f2ba7f9
Differential Revision: https://phabricator.services.mozilla.com/D181158
267 lines
7.0 KiB
JavaScript
267 lines
7.0 KiB
JavaScript
/* 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/. */
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
|
HTTP_404: "chrome://remote/content/server/httpd.sys.mjs",
|
|
HTTP_405: "chrome://remote/content/server/httpd.sys.mjs",
|
|
HTTP_500: "chrome://remote/content/server/httpd.sys.mjs",
|
|
Protocol: "chrome://remote/content/cdp/Protocol.sys.mjs",
|
|
RemoteAgentError: "chrome://remote/content/cdp/Error.sys.mjs",
|
|
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
|
|
});
|
|
|
|
export class JSONHandler {
|
|
constructor(cdp) {
|
|
this.cdp = cdp;
|
|
this.routes = {
|
|
"/json/version": {
|
|
handler: this.getVersion.bind(this),
|
|
},
|
|
|
|
"/json/protocol": {
|
|
handler: this.getProtocol.bind(this),
|
|
},
|
|
|
|
"/json/list": {
|
|
handler: this.getTargetList.bind(this),
|
|
},
|
|
|
|
"/json": {
|
|
handler: this.getTargetList.bind(this),
|
|
},
|
|
|
|
// PUT only - /json/new?{url}
|
|
"/json/new": {
|
|
handler: this.newTarget.bind(this),
|
|
method: "PUT",
|
|
},
|
|
|
|
// /json/activate/{targetId}
|
|
"/json/activate": {
|
|
handler: this.activateTarget.bind(this),
|
|
parameter: true,
|
|
},
|
|
|
|
// /json/close/{targetId}
|
|
"/json/close": {
|
|
handler: this.closeTarget.bind(this),
|
|
parameter: true,
|
|
},
|
|
};
|
|
}
|
|
|
|
getVersion() {
|
|
const mainProcessTarget = this.cdp.targetList.getMainProcessTarget();
|
|
|
|
const { userAgent } = Cc[
|
|
"@mozilla.org/network/protocol;1?name=http"
|
|
].getService(Ci.nsIHttpProtocolHandler);
|
|
|
|
return {
|
|
body: {
|
|
Browser: `${Services.appinfo.name}/${Services.appinfo.version}`,
|
|
"Protocol-Version": "1.3",
|
|
"User-Agent": userAgent,
|
|
"V8-Version": "1.0",
|
|
"WebKit-Version": "1.0",
|
|
webSocketDebuggerUrl: mainProcessTarget.toJSON().webSocketDebuggerUrl,
|
|
},
|
|
};
|
|
}
|
|
|
|
getProtocol() {
|
|
return { body: lazy.Protocol.Description };
|
|
}
|
|
|
|
getTargetList() {
|
|
return { body: [...this.cdp.targetList].filter(x => x.type !== "browser") };
|
|
}
|
|
|
|
/** HTTP copy of Target.createTarget() */
|
|
async newTarget(url) {
|
|
const onTarget = this.cdp.targetList.once("target-created");
|
|
|
|
// Open new tab
|
|
const tab = await lazy.TabManager.addTab({
|
|
focus: true,
|
|
});
|
|
|
|
// Get the newly created target
|
|
const target = await onTarget;
|
|
if (tab.linkedBrowser != target.browser) {
|
|
throw new Error(
|
|
"Unexpected tab opened: " + tab.linkedBrowser.currentURI.spec
|
|
);
|
|
}
|
|
|
|
const returnJson = target.toJSON();
|
|
|
|
// Load URL if given, otherwise stay on about:blank
|
|
if (url) {
|
|
let validURL;
|
|
try {
|
|
validURL = Services.io.newURI(url);
|
|
} catch {
|
|
// If we failed to parse given URL, return now since we already loaded about:blank
|
|
return { body: returnJson };
|
|
}
|
|
|
|
target.browsingContext.loadURI(validURL, {
|
|
triggeringPrincipal:
|
|
Services.scriptSecurityManager.getSystemPrincipal(),
|
|
});
|
|
|
|
// Force the URL in the returned target JSON to match given
|
|
// even if loading/will fail (matches Chromium behavior)
|
|
returnJson.url = url;
|
|
}
|
|
|
|
return { body: returnJson };
|
|
}
|
|
|
|
/** HTTP copy of Target.activateTarget() */
|
|
async activateTarget(targetId) {
|
|
// Try to get the target from given id
|
|
const target = this.cdp.targetList.getById(targetId);
|
|
|
|
if (!target) {
|
|
return {
|
|
status: lazy.HTTP_404,
|
|
body: `No such target id: ${targetId}`,
|
|
json: false,
|
|
};
|
|
}
|
|
|
|
// Select the tab (this endpoint does not show the window)
|
|
await lazy.TabManager.selectTab(target.tab);
|
|
|
|
return { body: "Target activated", json: false };
|
|
}
|
|
|
|
/** HTTP copy of Target.closeTarget() */
|
|
async closeTarget(targetId) {
|
|
// Try to get the target from given id
|
|
const target = this.cdp.targetList.getById(targetId);
|
|
|
|
if (!target) {
|
|
return {
|
|
status: lazy.HTTP_404,
|
|
body: `No such target id: ${targetId}`,
|
|
json: false,
|
|
};
|
|
}
|
|
|
|
// Remove the tab
|
|
await lazy.TabManager.removeTab(target.tab);
|
|
|
|
return { body: "Target is closing", json: false };
|
|
}
|
|
|
|
// nsIHttpRequestHandler
|
|
|
|
async handle(request, response) {
|
|
// Mark request as async so we can execute async routes and return values
|
|
response.processAsync();
|
|
|
|
// Run a provided route (function) with an argument
|
|
const runRoute = async (route, data) => {
|
|
try {
|
|
// Run the route to get data to return
|
|
const {
|
|
status = { code: 200, description: "OK" },
|
|
json = true,
|
|
body,
|
|
} = await route(data);
|
|
|
|
// Stringify into returnable JSON if wanted
|
|
const payload = json
|
|
? JSON.stringify(body, null, lazy.Log.verbose ? "\t" : null)
|
|
: body;
|
|
|
|
// Handle HTTP response
|
|
response.setStatusLine(
|
|
request.httpVersion,
|
|
status.code,
|
|
status.description
|
|
);
|
|
response.setHeader("Content-Type", "application/json");
|
|
response.setHeader("Content-Security-Policy", "frame-ancestors 'none'");
|
|
response.write(payload);
|
|
} catch (e) {
|
|
new lazy.RemoteAgentError(e).notify();
|
|
|
|
// Mark as 500 as an error has occured internally
|
|
response.setStatusLine(
|
|
request.httpVersion,
|
|
lazy.HTTP_500.code,
|
|
lazy.HTTP_500.description
|
|
);
|
|
}
|
|
};
|
|
|
|
// Trim trailing slashes to conform with expected routes
|
|
const path = request.path.replace(/\/+$/, "");
|
|
|
|
let route;
|
|
for (const _route in this.routes) {
|
|
// Prefixed/parameter route (/path/{parameter})
|
|
if (path.startsWith(_route + "/") && this.routes[_route].parameter) {
|
|
route = _route;
|
|
break;
|
|
}
|
|
|
|
// Regular route (/path/example)
|
|
if (path === _route) {
|
|
route = _route;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!route) {
|
|
// Route does not exist
|
|
response.setStatusLine(
|
|
request.httpVersion,
|
|
lazy.HTTP_404.code,
|
|
lazy.HTTP_404.description
|
|
);
|
|
response.write("Unknown command: " + path.replace("/json/", ""));
|
|
|
|
return response.finish();
|
|
}
|
|
|
|
const { handler, method, parameter } = this.routes[route];
|
|
|
|
// If only one valid method for route, check method matches
|
|
if (method && request.method !== method) {
|
|
response.setStatusLine(
|
|
request.httpVersion,
|
|
lazy.HTTP_405.code,
|
|
lazy.HTTP_405.description
|
|
);
|
|
response.write(
|
|
`Using unsafe HTTP verb ${request.method} to invoke ${route}. This action supports only PUT verb.`
|
|
);
|
|
return response.finish();
|
|
}
|
|
|
|
if (parameter) {
|
|
await runRoute(handler, path.split("/").pop());
|
|
} else {
|
|
await runRoute(handler, request.queryString);
|
|
}
|
|
|
|
// Send response
|
|
return response.finish();
|
|
}
|
|
|
|
// XPCOM
|
|
|
|
get QueryInterface() {
|
|
return ChromeUtils.generateQI(["nsIHttpRequestHandler"]);
|
|
}
|
|
}
|