diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /devtools/client/shared/remote-debugging/adb/commands | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/shared/remote-debugging/adb/commands')
7 files changed, 452 insertions, 0 deletions
diff --git a/devtools/client/shared/remote-debugging/adb/commands/index.js b/devtools/client/shared/remote-debugging/adb/commands/index.js new file mode 100644 index 0000000000..1032f1a4b8 --- /dev/null +++ b/devtools/client/shared/remote-debugging/adb/commands/index.js @@ -0,0 +1,29 @@ +/* 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"; + +const { + listDevices, +} = require("resource://devtools/client/shared/remote-debugging/adb/commands/list-devices.js"); +const { + prepareTCPConnection, +} = require("resource://devtools/client/shared/remote-debugging/adb/commands/prepare-tcp-connection.js"); +const { + runCommand, +} = require("resource://devtools/client/shared/remote-debugging/adb/commands/run-command.js"); +const { + shell, +} = require("resource://devtools/client/shared/remote-debugging/adb/commands/shell.js"); +const { + TrackDevicesCommand, +} = require("resource://devtools/client/shared/remote-debugging/adb/commands/track-devices.js"); + +module.exports = { + listDevices, + prepareTCPConnection, + runCommand, + shell, + TrackDevicesCommand, +}; diff --git a/devtools/client/shared/remote-debugging/adb/commands/list-devices.js b/devtools/client/shared/remote-debugging/adb/commands/list-devices.js new file mode 100644 index 0000000000..c04ba9eb1e --- /dev/null +++ b/devtools/client/shared/remote-debugging/adb/commands/list-devices.js @@ -0,0 +1,29 @@ +/* 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"; + +const { dumpn } = require("resource://devtools/shared/DevToolsUtils.js"); +/** + * The listDevices command is currently unused in DevTools. We are keeping it while + * working on RemoteDebugging NG, in case it becomes needed later. We will remove it from + * the codebase if unused at the end of the project. See Bug 1511779. + */ +const listDevices = function () { + dumpn("listDevices"); + + return this.runCommand("host:devices").then(function onSuccess(data) { + const lines = data.split("\n"); + const res = []; + lines.forEach(function (line) { + if (!line.length) { + return; + } + const [device] = line.split("\t"); + res.push(device); + }); + return res; + }); +}; +exports.listDevices = listDevices; diff --git a/devtools/client/shared/remote-debugging/adb/commands/moz.build b/devtools/client/shared/remote-debugging/adb/commands/moz.build new file mode 100644 index 0000000000..ecd3428a1b --- /dev/null +++ b/devtools/client/shared/remote-debugging/adb/commands/moz.build @@ -0,0 +1,12 @@ +# 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/. + +DevToolsModules( + "index.js", + "list-devices.js", + "prepare-tcp-connection.js", + "run-command.js", + "shell.js", + "track-devices.js", +) diff --git a/devtools/client/shared/remote-debugging/adb/commands/prepare-tcp-connection.js b/devtools/client/shared/remote-debugging/adb/commands/prepare-tcp-connection.js new file mode 100644 index 0000000000..dc9103f56b --- /dev/null +++ b/devtools/client/shared/remote-debugging/adb/commands/prepare-tcp-connection.js @@ -0,0 +1,46 @@ +/* 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"; + +const { dumpn } = require("resource://devtools/shared/DevToolsUtils.js"); +const { + runCommand, +} = require("resource://devtools/client/shared/remote-debugging/adb/commands/run-command.js"); + +// sends adb forward deviceId, localPort and devicePort +const forwardPort = function (deviceId, localPort, devicePort) { + dumpn("forwardPort " + localPort + " -- " + devicePort); + // Send "host-serial:<serial-number>:<request>", + // with <request> set to "forward:<local>;<remote>" + // See https://android.googlesource.com/platform/system/core/+/jb-dev/adb/SERVICES.TXT + return runCommand( + `host-serial:${deviceId}:forward:${localPort};${devicePort}` + ).then(function onSuccess(data) { + return data; + }); +}; + +const getFreeTCPPort = function () { + const serv = Cc["@mozilla.org/network/server-socket;1"].createInstance( + Ci.nsIServerSocket + ); + serv.init(-1, true, -1); + const port = serv.port; + serv.close(); + return port; +}; + +// Prepare TCP connection for provided device id and socket path. +// The returned value is a port number of localhost for the connection. +const prepareTCPConnection = async function (deviceId, socketPath) { + const port = getFreeTCPPort(); + const local = `tcp:${port}`; + const remote = socketPath.startsWith("@") + ? `localabstract:${socketPath.substring(1)}` + : `localfilesystem:${socketPath}`; + await forwardPort(deviceId, local, remote); + return port; +}; +exports.prepareTCPConnection = prepareTCPConnection; diff --git a/devtools/client/shared/remote-debugging/adb/commands/run-command.js b/devtools/client/shared/remote-debugging/adb/commands/run-command.js new file mode 100644 index 0000000000..5fc521b5e7 --- /dev/null +++ b/devtools/client/shared/remote-debugging/adb/commands/run-command.js @@ -0,0 +1,66 @@ +/* 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/. */ + +// Wrapper around the ADB utility. + +"use strict"; + +const { dumpn } = require("resource://devtools/shared/DevToolsUtils.js"); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); +const { + adbProcess, +} = require("resource://devtools/client/shared/remote-debugging/adb/adb-process.js"); +const client = require("resource://devtools/client/shared/remote-debugging/adb/adb-client.js"); + +const OKAY = 0x59414b4f; + +// Asynchronously runs an adb command. +// @param command The command as documented in +// http://androidxref.com/4.0.4/xref/system/core/adb/SERVICES.TXT +const runCommand = function (command) { + dumpn("runCommand " + command); + return new Promise((resolve, reject) => { + if (!adbProcess.ready) { + setTimeout(function () { + reject("ADB_NOT_READY"); + }); + return; + } + + const socket = client.connect(); + + socket.s.onopen = function () { + dumpn("runCommand onopen"); + const req = client.createRequest(command); + socket.send(req); + }; + + socket.s.onerror = function () { + dumpn("runCommand onerror"); + reject("NETWORK_ERROR"); + }; + + socket.s.onclose = function () { + dumpn("runCommand onclose"); + }; + + socket.s.ondata = function (event) { + dumpn("runCommand ondata"); + const data = event.data; + + const packet = client.unpackPacket(data, false); + if (!client.checkResponse(data, OKAY)) { + socket.close(); + dumpn("Error: " + packet.data); + reject("PROTOCOL_ERROR"); + return; + } + + resolve(packet.data); + }; + }); +}; +exports.runCommand = runCommand; diff --git a/devtools/client/shared/remote-debugging/adb/commands/shell.js b/devtools/client/shared/remote-debugging/adb/commands/shell.js new file mode 100644 index 0000000000..03f4bfcf78 --- /dev/null +++ b/devtools/client/shared/remote-debugging/adb/commands/shell.js @@ -0,0 +1,107 @@ +/* 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/. */ + +// Wrapper around the ADB utility. + +"use strict"; + +const { dumpn } = require("resource://devtools/shared/DevToolsUtils.js"); +const client = require("resource://devtools/client/shared/remote-debugging/adb/adb-client.js"); + +const OKAY = 0x59414b4f; + +const shell = async function (deviceId, command) { + if (!deviceId) { + throw new Error("ADB shell command needs the device id"); + } + + let state; + let stdout = ""; + + dumpn("shell " + command + " on " + deviceId); + + return new Promise((resolve, reject) => { + const shutdown = function () { + dumpn("shell shutdown"); + socket.close(); + reject("BAD_RESPONSE"); + }; + + const runFSM = function runFSM(data) { + dumpn("runFSM " + state); + let req; + let ignoreResponseCode = false; + switch (state) { + case "start": + state = "send-transport"; + runFSM(); + break; + case "send-transport": + req = client.createRequest("host:transport:" + deviceId); + socket.send(req); + state = "wait-transport"; + break; + case "wait-transport": + if (!client.checkResponse(data, OKAY)) { + shutdown(); + return; + } + state = "send-shell"; + runFSM(); + break; + case "send-shell": + req = client.createRequest("shell:" + command); + socket.send(req); + state = "rec-shell"; + break; + case "rec-shell": + if (!client.checkResponse(data, OKAY)) { + shutdown(); + return; + } + state = "decode-shell"; + if (client.getBuffer(data).byteLength == 4) { + break; + } + ignoreResponseCode = true; + // eslint-disable-next-lined no-fallthrough + case "decode-shell": + const decoder = new TextDecoder(); + const text = new Uint8Array( + client.getBuffer(data), + ignoreResponseCode ? 4 : 0 + ); + stdout += decoder.decode(text); + break; + default: + dumpn("shell Unexpected State: " + state); + reject("UNEXPECTED_STATE"); + } + }; + + const socket = client.connect(); + socket.s.onerror = function (event) { + dumpn("shell onerror"); + reject("SOCKET_ERROR"); + }; + + socket.s.onopen = function (event) { + dumpn("shell onopen"); + state = "start"; + runFSM(); + }; + + socket.s.onclose = function (event) { + resolve(stdout); + dumpn("shell onclose"); + }; + + socket.s.ondata = function (event) { + dumpn("shell ondata"); + runFSM(event.data); + }; + }); +}; + +exports.shell = shell; diff --git a/devtools/client/shared/remote-debugging/adb/commands/track-devices.js b/devtools/client/shared/remote-debugging/adb/commands/track-devices.js new file mode 100644 index 0000000000..2d796668ea --- /dev/null +++ b/devtools/client/shared/remote-debugging/adb/commands/track-devices.js @@ -0,0 +1,163 @@ +/* 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/. */ + +// Wrapper around the ADB utility. + +"use strict"; + +const EventEmitter = require("resource://devtools/shared/event-emitter.js"); +const { dumpn } = require("resource://devtools/shared/DevToolsUtils.js"); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +const { + adbProcess, +} = require("resource://devtools/client/shared/remote-debugging/adb/adb-process.js"); +const client = require("resource://devtools/client/shared/remote-debugging/adb/adb-client.js"); + +const ADB_STATUS_OFFLINE = "offline"; +const OKAY = 0x59414b4f; + +// Start tracking devices connecting and disconnecting from the host. +// We can't reuse runCommand here because we keep the socket alive. +class TrackDevicesCommand extends EventEmitter { + run() { + this._waitForFirst = true; + // Hold device statuses. key: device id, value: status. + this._devices = new Map(); + this._socket = client.connect(); + + this._socket.s.onopen = this._onOpen.bind(this); + this._socket.s.onerror = this._onError.bind(this); + this._socket.s.onclose = this._onClose.bind(this); + this._socket.s.ondata = this._onData.bind(this); + } + + stop() { + if (this._socket) { + this._socket.close(); + + this._socket.s.onopen = null; + this._socket.s.onerror = null; + this._socket.s.onclose = null; + this._socket.s.ondata = null; + } + } + + _onOpen() { + dumpn("trackDevices onopen"); + const req = client.createRequest("host:track-devices"); + this._socket.send(req); + } + + _onError(event) { + dumpn("trackDevices onerror: " + event); + } + + _onClose() { + dumpn("trackDevices onclose"); + + // Report all devices as disconnected + this._disconnectAllDevices(); + + // When we lose connection to the server, + // and the adb is still on, we most likely got our server killed + // by local adb. So we do try to reconnect to it. + + // Give some time to the new adb to start + setTimeout(() => { + // Only try to reconnect/restart if the add-on is still enabled + if (adbProcess.ready) { + // try to connect to the new local adb server or spawn a new one + adbProcess.start().then(() => { + // Re-track devices + this.run(); + }); + } + }, 2000); + } + + _onData(event) { + dumpn("trackDevices ondata"); + const data = event.data; + dumpn("length=" + data.byteLength); + const dec = new TextDecoder(); + dumpn(dec.decode(new Uint8Array(data)).trim()); + + // check the OKAY or FAIL on first packet. + if (this._waitForFirst) { + if (!client.checkResponse(data, OKAY)) { + this._socket.close(); + return; + } + } + + const packet = client.unpackPacket(data, !this._waitForFirst); + this._waitForFirst = false; + + if (packet.data == "") { + // All devices got disconnected. + this._disconnectAllDevices(); + } else { + // One line per device, each line being $DEVICE\t(offline|device) + const lines = packet.data.split("\n"); + const newDevices = new Map(); + lines.forEach(function (line) { + if (!line.length) { + return; + } + + const [deviceId, status] = line.split("\t"); + newDevices.set(deviceId, status); + }); + + // Fire events if needed. + const deviceIds = new Set([ + ...this._devices.keys(), + ...newDevices.keys(), + ]); + for (const deviceId of deviceIds) { + const currentStatus = this._devices.get(deviceId); + const newStatus = newDevices.get(deviceId); + this._fireConnectionEventIfNeeded(deviceId, currentStatus, newStatus); + } + + // Update devices. + this._devices = newDevices; + } + } + + _disconnectAllDevices() { + if (this._devices.size === 0) { + // If no devices were detected, fire an event to let consumer resume. + this.emit("no-devices-detected"); + } else { + for (const [deviceId, status] of this._devices.entries()) { + if (status !== ADB_STATUS_OFFLINE) { + this.emit("device-disconnected", deviceId); + } + } + } + this._devices = new Map(); + } + + _fireConnectionEventIfNeeded(deviceId, currentStatus, newStatus) { + const isCurrentOnline = !!( + currentStatus && currentStatus !== ADB_STATUS_OFFLINE + ); + const isNewOnline = !!(newStatus && newStatus !== ADB_STATUS_OFFLINE); + + if (isCurrentOnline === isNewOnline) { + return; + } + + if (isNewOnline) { + this.emit("device-connected", deviceId); + } else { + this.emit("device-disconnected", deviceId); + } + } +} +exports.TrackDevicesCommand = TrackDevicesCommand; |