Source code

Revision control

Copy as Markdown

Other Tools

/* 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 lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
});
loader.lazyGetter(this, "UNPACKED_ROOT_PATH", () => {
return PathUtils.join(PathUtils.localProfileDir, "adb");
});
loader.lazyGetter(this, "EXTENSION_ID", () => {
return Services.prefs.getCharPref("devtools.remote.adb.extensionID");
});
loader.lazyGetter(this, "ADB_BINARY_PATH", () => {
let adbBinaryPath = PathUtils.join(UNPACKED_ROOT_PATH, "adb");
if (Services.appinfo.OS === "WINNT") {
adbBinaryPath += ".exe";
}
return adbBinaryPath;
});
const MANIFEST = "manifest.json";
/**
* Read contents from a given uri in the devtools-adb-extension and parse the
* contents as JSON.
*/
async function readFromExtension(fileUri) {
return new Promise(resolve => {
lazy.NetUtil.asyncFetch(
{
uri: fileUri,
loadUsingSystemPrincipal: true,
},
input => {
try {
const string = lazy.NetUtil.readInputStreamToString(
input,
input.available()
);
resolve(JSON.parse(string));
} catch (e) {
dumpn(`Could not read ${fileUri} in the extension: ${e}`);
resolve(null);
}
}
);
});
}
/**
* Unpack file from the extension.
* Uses NetUtil to read and write, since it's required for reading.
*
* @param {string} file
* The path name of the file in the extension.
*/
async function unpackFile(file) {
const policy = lazy.ExtensionParent.WebExtensionPolicy.getByID(EXTENSION_ID);
if (!policy) {
return;
}
// Assumes that destination dir already exists.
const basePath = file.substring(file.lastIndexOf("/") + 1);
const filePath = PathUtils.join(UNPACKED_ROOT_PATH, basePath);
await new Promise((resolve, reject) => {
lazy.NetUtil.asyncFetch(
{
uri: policy.getURL(file),
loadUsingSystemPrincipal: true,
},
input => {
try {
// Since we have to use NetUtil to read, probably it's okay to use for
// writing, rather than bouncing to IOUtils...?
const outputFile = new lazy.FileUtils.File(filePath);
const output = lazy.FileUtils.openAtomicFileOutputStream(outputFile);
lazy.NetUtil.asyncCopy(input, output, resolve);
} catch (e) {
dumpn(`Could not unpack file ${file} in the extension: ${e}`);
reject(e);
}
}
);
});
// Mark binaries as executable.
await IOUtils.setPermissions(filePath, 0o744);
}
/**
* Extract files in the extension into local profile directory and returns
* if it fails.
*/
async function extractFiles() {
const policy = lazy.ExtensionParent.WebExtensionPolicy.getByID(EXTENSION_ID);
if (!policy) {
return false;
}
const uri = policy.getURL("adb.json");
const adbInfo = await readFromExtension(uri);
if (!adbInfo) {
return false;
}
let filesForAdb;
try {
// The adbInfo is an object looks like this;
//
// {
// "Linux": {
// "x86": [
// "linux/adb"
// ],
// "x86_64": [
// "linux64/adb"
// ]
// },
// ...
// XPCOMABI looks this; x86_64-gcc3, so drop the compiler name.
let architecture = Services.appinfo.XPCOMABI.split("-")[0];
if (architecture === "aarch64") {
// Fallback on x86 or x86_64 binaries for aarch64 - See Bug 1522149
const hasX86Binary = !!adbInfo[Services.appinfo.OS].x86;
architecture = hasX86Binary ? "x86" : "x86_64";
}
filesForAdb = adbInfo[Services.appinfo.OS][architecture];
} catch (e) {
return false;
}
// manifest.json isn't in adb.json but has to be unpacked for version
// comparison
filesForAdb.push(MANIFEST);
await IOUtils.makeDirectory(UNPACKED_ROOT_PATH);
for (const file of filesForAdb) {
try {
await unpackFile(file);
} catch (e) {
return false;
}
}
return true;
}
/**
* Read the manifest from inside the devtools-adb-extension.
* Uses NetUtil since data is packed inside the extension, not a local file.
*/
async function getManifestFromExtension() {
const policy = lazy.ExtensionParent.WebExtensionPolicy.getByID(EXTENSION_ID);
if (!policy) {
return null;
}
const manifestUri = policy.getURL(MANIFEST);
return readFromExtension(manifestUri);
}
/**
* Returns whether manifest.json has already been unpacked.
*/
async function isManifestUnpacked() {
const manifestPath = PathUtils.join(UNPACKED_ROOT_PATH, MANIFEST);
return IOUtils.exists(manifestPath);
}
/**
* Read the manifest from the unpacked binary directory.
* Uses IOUtils since this is a local file.
*/
async function getManifestFromUnpacked() {
if (!(await isManifestUnpacked())) {
throw new Error("Manifest doesn't exist at unpacked path");
}
const manifestPath = PathUtils.join(UNPACKED_ROOT_PATH, MANIFEST);
const binary = await IOUtils.read(manifestPath);
const json = new TextDecoder().decode(binary);
let data;
try {
data = JSON.parse(json);
} catch (e) {}
return data;
}
/**
* Check state of binary unpacking, including the location and manifest.
*/
async function isUnpacked() {
if (!(await isManifestUnpacked())) {
dumpn("Needs unpacking, no manifest found");
return false;
}
const manifestInExtension = await getManifestFromExtension();
const unpackedManifest = await getManifestFromUnpacked();
if (manifestInExtension.version != unpackedManifest.version) {
dumpn(
`Needs unpacking, extension version ${manifestInExtension.version} != ` +
`unpacked version ${unpackedManifest.version}`
);
return false;
}
dumpn("Already unpacked");
return true;
}
/**
* Get a file object for the adb binary from the 'adb@mozilla.org' extension
* which has been already installed.
*
* @return {nsIFile}
* File object for the binary.
*/
async function getFileForBinary() {
if (!(await isUnpacked()) && !(await extractFiles())) {
return null;
}
const file = new lazy.FileUtils.File(ADB_BINARY_PATH);
if (!file.exists()) {
return null;
}
return file;
}
exports.getFileForBinary = getFileForBinary;