Bug 1492700 - Introduce adb singleton to register adb consumers;r=daisuke

Depends on D13476

Differential Revision: https://phabricator.services.mozilla.com/D13477

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Julian Descottes 2018-12-06 14:57:03 +00:00
Родитель 5931fd827f
Коммит cefef81b96
10 изменённых файлов: 144 добавлений и 532 удалений

Просмотреть файл

@ -28,8 +28,6 @@ const {
} = require("./src/modules/network-locations");
const {
addUSBRuntimesObserver,
disableUSBRuntimes,
enableUSBRuntimes,
getUSBRuntimes,
removeUSBRuntimesObserver,
} = require("./src/modules/usb-runtimes");
@ -76,8 +74,10 @@ const AboutDebugging = {
this.actions.updateNetworkLocations(getNetworkLocations());
addNetworkLocationsObserver(this.onNetworkLocationsUpdated);
// Listen to USB runtime updates and retrieve the initial list of runtimes.
addUSBRuntimesObserver(this.onUSBRuntimesUpdated);
await enableUSBRuntimes();
getUSBRuntimes();
adbAddon.on("update", this.onAdbAddonUpdated);
this.onAdbAddonUpdated();
@ -113,7 +113,6 @@ const AboutDebugging = {
removeNetworkLocationsObserver(this.onNetworkLocationsUpdated);
removeUSBRuntimesObserver(this.onUSBRuntimesUpdated);
disableUSBRuntimes();
adbAddon.off("update", this.onAdbAddonUpdated);
setDebugTargetCollapsibilities(state.ui.debugTargetCollapsibilities);
unmountComponentAtNode(this.mount);

Просмотреть файл

@ -4,42 +4,29 @@
"use strict";
loader.lazyGetter(this, "adbScanner", () => {
const { AddonAwareADBScanner } = require("devtools/shared/adb/addon-aware-adb-scanner");
return new AddonAwareADBScanner();
});
loader.lazyRequireGetter(this, "adb", "devtools/shared/adb/adb", true);
/**
* This module provides a collection of helper methods to detect USB runtimes whom Firefox
* is running on.
*/
function addUSBRuntimesObserver(listener) {
adbScanner.on("runtime-list-updated", listener);
adb.registerListener(listener);
}
exports.addUSBRuntimesObserver = addUSBRuntimesObserver;
function disableUSBRuntimes() {
adbScanner.disable();
}
exports.disableUSBRuntimes = disableUSBRuntimes;
async function enableUSBRuntimes() {
adbScanner.enable();
}
exports.enableUSBRuntimes = enableUSBRuntimes;
function getUSBRuntimes() {
return adbScanner.listRuntimes();
return adb.getRuntimes();
}
exports.getUSBRuntimes = getUSBRuntimes;
function removeUSBRuntimesObserver(listener) {
adbScanner.off("runtime-list-updated", listener);
adb.unregisterListener(listener);
}
exports.removeUSBRuntimesObserver = removeUSBRuntimesObserver;
function refreshUSBRuntimes() {
return adbScanner.scan();
return adb.updateRuntimes();
}
exports.refreshUSBRuntimes = refreshUSBRuntimes;

Просмотреть файл

@ -11,10 +11,7 @@ const EventEmitter = require("devtools/shared/event-emitter");
const {RuntimeTypes} = require("devtools/client/webide/modules/runtime-types");
const promise = require("promise");
loader.lazyGetter(this, "adbScanner", () => {
const { AddonAwareADBScanner } = require("devtools/shared/adb/addon-aware-adb-scanner");
return new AddonAwareADBScanner();
});
loader.lazyRequireGetter(this, "adb", "devtools/shared/adb/adb", true);
loader.lazyRequireGetter(this, "AuthenticationResult",
"devtools/shared/security/auth", true);
@ -198,9 +195,29 @@ exports.RuntimeScanners = RuntimeScanners;
/* SCANNERS */
// The adb-scanner will automatically start and stop when the ADB extension is installed
// and uninstalled, so the scanner itself can always be used.
RuntimeScanners.add(adbScanner);
var UsbScanner = {
init() {
this._emitUpdated = this._emitUpdated.bind(this);
},
enable() {
adb.registerListener(this._emitUpdated);
},
disable() {
adb.unregisterListener(this._emitUpdated);
},
scan() {
return adb.updateRuntimes();
},
listRuntimes() {
return adb.getRuntimes();
},
_emitUpdated() {
this.emit("runtime-list-updated");
},
};
EventEmitter.decorate(UsbScanner);
UsbScanner.init();
RuntimeScanners.add(UsbScanner);
var WiFiScanner = {

Просмотреть файл

@ -1,80 +0,0 @@
/* 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 EventEmitter = require("devtools/shared/event-emitter");
const { adbAddon, ADB_ADDON_STATES } = require("devtools/shared/adb/adb-addon");
/**
* Shared registry that will hold all the detected devices from ADB.
* Extends EventEmitter and emits the following events:
* - "register": a new device has been registered
* - "unregister": a device has been unregistered
*/
class AdbDevicesRegistry extends EventEmitter {
constructor() {
super();
// Internal object to store the discovered adb devices.
this._devices = {};
// When the addon is uninstalled, the repository should be emptied.
// TODO: This should also be done when ADB is stopped.
this._onAdbAddonUpdate = this._onAdbAddonUpdate.bind(this);
adbAddon.on("update", this._onAdbAddonUpdate);
}
/**
* Register a device (Device class defined in from adb-device.js) for the provided name.
*
* @param {String} name
* Name of the device.
* @param {AdbDevice} device
* The device to register.
*/
register(name, device) {
this._devices[name] = device;
this.emit("register");
}
/**
* Unregister a device previously registered under the provided name.
*
* @param {String} name
* Name of the device.
*/
unregister(name) {
delete this._devices[name];
this.emit("unregister");
}
/**
* Returns an iterable containing the name of all the available devices, sorted by name.
*/
available() {
return Object.keys(this._devices).sort();
}
/**
* Returns a device previously registered under the provided name.
*
* @param {String} name
* Name of the device.
*/
getByName(name) {
return this._devices[name];
}
_onAdbAddonUpdate() {
const installed = adbAddon.status === ADB_ADDON_STATES.INSTALLED;
if (!installed) {
for (const name in this._devices) {
this.unregister(name);
}
}
}
}
exports.adbDevicesRegistry = new AdbDevicesRegistry();

Просмотреть файл

@ -1,100 +0,0 @@
/* 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 EventEmitter = require("devtools/shared/event-emitter");
const { dumpn } = require("devtools/shared/DevToolsUtils");
const { adbProcess } = require("devtools/shared/adb/adb-process");
const { TrackDevicesCommand } = require("devtools/shared/adb/commands/index");
const { adbDevicesRegistry } = require("devtools/shared/adb/adb-devices-registry");
const { AdbRuntime } = require("devtools/shared/adb/adb-runtime");
loader.lazyRequireGetter(this, "AdbDevice", "devtools/shared/adb/adb-device");
class ADBScanner extends EventEmitter {
constructor() {
super();
this._runtimes = [];
this._trackDevicesCommand = new TrackDevicesCommand();
this._onDeviceConnected = this._onDeviceConnected.bind(this);
this._onDeviceDisconnected = this._onDeviceDisconnected.bind(this);
this._updateRuntimes = this._updateRuntimes.bind(this);
}
enable() {
this._trackDevicesCommand.on("device-connected", this._onDeviceConnected);
this._trackDevicesCommand.on("device-disconnected", this._onDeviceDisconnected);
adbDevicesRegistry.on("register", this._updateRuntimes);
adbDevicesRegistry.on("unregister", this._updateRuntimes);
adbProcess.start().then(() => {
this._trackDevicesCommand.run();
});
this._updateRuntimes();
}
disable() {
this._trackDevicesCommand.off("device-connected", this._onDeviceConnected);
this._trackDevicesCommand.off("device-disconnected", this._onDeviceDisconnected);
adbDevicesRegistry.off("register", this._updateRuntimes);
adbDevicesRegistry.off("unregister", this._updateRuntimes);
this._updateRuntimes();
}
_emitUpdated() {
this.emit("runtime-list-updated");
}
_onDeviceConnected(deviceId) {
const device = new AdbDevice(deviceId);
adbDevicesRegistry.register(deviceId, device);
}
_onDeviceDisconnected(deviceId) {
adbDevicesRegistry.unregister(deviceId);
}
_updateRuntimes() {
if (this._updatingPromise) {
return this._updatingPromise;
}
this._runtimes = [];
const promises = [];
for (const id of adbDevicesRegistry.available()) {
const device = adbDevicesRegistry.getByName(id);
promises.push(this._detectRuntimes(device));
}
this._updatingPromise = Promise.all(promises);
this._updatingPromise.then(() => {
this._emitUpdated();
this._updatingPromise = null;
}, () => {
this._updatingPromise = null;
});
return this._updatingPromise;
}
async _detectRuntimes(adbDevice) {
const model = await adbDevice.getModel();
const socketPaths = await adbDevice.getRuntimeSocketPaths();
for (const socketPath of socketPaths) {
const runtime = new AdbRuntime(adbDevice, model, socketPath);
dumpn("Found " + runtime.name);
this._runtimes.push(runtime);
}
}
scan() {
return this._updateRuntimes();
}
listRuntimes() {
return this._runtimes;
}
}
exports.ADBScanner = ADBScanner;

111
devtools/shared/adb/adb.js Normal file
Просмотреть файл

@ -0,0 +1,111 @@
/* 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 EventEmitter = require("devtools/shared/event-emitter");
const { adbProcess } = require("devtools/shared/adb/adb-process");
const { adbAddon } = require("devtools/shared/adb/adb-addon");
const AdbDevice = require("devtools/shared/adb/adb-device");
const { AdbRuntime } = require("devtools/shared/adb/adb-runtime");
const { TrackDevicesCommand } = require("devtools/shared/adb/commands/track-devices");
class Adb extends EventEmitter {
constructor() {
super();
this._trackDevicesCommand = new TrackDevicesCommand();
this._isTrackingDevices = false;
this._isUpdatingRuntimes = false;
this._listeners = new Set();
this._devices = new Map();
this._runtimes = [];
this._updateAdbProcess = this._updateAdbProcess.bind(this);
this._onDeviceConnected = this._onDeviceConnected.bind(this);
this._onDeviceDisconnected = this._onDeviceDisconnected.bind(this);
this._trackDevicesCommand.on("device-connected", this._onDeviceConnected);
this._trackDevicesCommand.on("device-disconnected", this._onDeviceDisconnected);
adbAddon.on("update", this._updateAdbProcess);
}
registerListener(listener) {
this._listeners.add(listener);
this.on("runtime-list-updated", listener);
this._updateAdbProcess();
}
unregisterListener(listener) {
this._listeners.delete(listener);
this.off("runtime-list-updated", listener);
this._updateAdbProcess();
}
async updateRuntimes() {
try {
const devices = [...this._devices.values()];
const promises = devices.map(d => this._getDeviceRuntimes(d));
const allRuntimes = await Promise.all(promises);
this._runtimes = allRuntimes.flat();
this.emit("runtime-list-updated");
} catch (e) {
console.error(e);
}
}
getRuntimes() {
return this._runtimes;
}
async _startAdb() {
this._isTrackingDevices = true;
await adbProcess.start();
this._trackDevicesCommand.run();
}
async _stopAdb() {
this._isTrackingDevices = false;
this._trackDevicesCommand.stop();
await adbProcess.stop();
this._devices = new Map();
this._runtimes = [];
this.updateRuntimes();
}
_shouldTrack() {
return adbAddon.status === "installed" && this._listeners.size > 0;
}
_updateAdbProcess() {
if (!this._isTrackingDevices && this._shouldTrack()) {
this._startAdb();
} else if (this._isTrackingDevices && !this._shouldTrack()) {
this._stopAdb();
}
}
_onDeviceConnected(deviceId) {
this._devices.set(deviceId, new AdbDevice(deviceId));
this.updateRuntimes();
}
_onDeviceDisconnected(deviceId) {
this._devices.delete(deviceId);
this.updateRuntimes();
}
async _getDeviceRuntimes(device) {
const model = await device.getModel();
const socketPaths = await device.getRuntimeSocketPaths();
return [...socketPaths].map(socketPath => new AdbRuntime(device, model, socketPath));
}
}
exports.adb = new Adb();

Просмотреть файл

@ -1,98 +0,0 @@
/* 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 EventEmitter = require("devtools/shared/event-emitter");
loader.lazyRequireGetter(this, "adbAddon", "devtools/shared/adb/adb-addon", true);
loader.lazyRequireGetter(this, "ADBScanner", "devtools/shared/adb/adb-scanner", true);
/**
* The AddonAwareADBScanner decorates an instance of ADBScanner. It will wait until the
* ADB addon is installed to enable() the real scanner, and will automatically disable
* it if the addon is uninstalled.
*
* It implements the following public API of ADBScanner:
* - enable
* - disable
* - scan
* - listRuntimes
* - event "runtime-list-updated"
*/
class AddonAwareADBScanner extends EventEmitter {
/**
* Parameters are provided only to allow tests to replace actual implementations with
* mocks.
*
* @param {ADBScanner} scanner
* Only provided in tests for mocks
* @param {ADBAddon} addon
* Only provided in tests for mocks
*/
constructor(scanner = new ADBScanner(), addon = adbAddon) {
super();
this._onScannerListUpdated = this._onScannerListUpdated.bind(this);
this._onAddonUpdate = this._onAddonUpdate.bind(this);
this._scanner = scanner;
this._scanner.on("runtime-list-updated", this._onScannerListUpdated);
this._addon = addon;
}
/**
* Only forward the enable() call if the addon is installed, because ADBScanner::enable
* only works if the addon is installed.
*/
enable() {
if (this._addon.status === "installed") {
this._scanner.enable();
}
// Remove any previous listener, to make sure we only add one listener if enable() is
// called several times.
this._addon.off("update", this._onAddonUpdate);
this._addon.on("update", this._onAddonUpdate);
}
disable() {
this._scanner.disable();
this._addon.off("update", this._onAddonUpdate);
}
/**
* Scan for USB devices.
*
* @return {Promise} Promise that will resolve when the scan is completed.
*/
scan() {
return this._scanner.scan();
}
/**
* Get the list of currently detected runtimes.
*
* @return {Array} Array of currently detected runtimes.
*/
listRuntimes() {
return this._scanner.listRuntimes();
}
_onAddonUpdate() {
if (this._addon.status === "installed") {
this._scanner.enable();
} else {
this._scanner.disable();
}
}
_onScannerListUpdated() {
this.emit("runtime-list-updated");
}
}
exports.AddonAwareADBScanner = AddonAwareADBScanner;

Просмотреть файл

@ -11,13 +11,11 @@ DevToolsModules(
'adb-binary.js',
'adb-client.js',
'adb-device.js',
'adb-devices-registry.js',
'adb-process.js',
'adb-running-checker.js',
'adb-runtime.js',
'adb-scanner.js',
'adb-socket.js',
'addon-aware-adb-scanner.js',
'adb.js',
)
with Files('**'):

Просмотреть файл

@ -1,221 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const EventEmitter = require("devtools/shared/event-emitter");
const { AddonAwareADBScanner } = require("devtools/shared/adb/addon-aware-adb-scanner");
/**
* For the scanner mock, we create an object with spies for each of the public methods
* used by the AddonAwareADBScanner, and the ability to emit events.
*/
function prepareMockScanner() {
const mockScanner = {
enable: sinon.spy(),
disable: sinon.spy(),
scan: sinon.spy(),
listRuntimes: sinon.spy(),
};
EventEmitter.decorate(mockScanner);
return mockScanner;
}
/**
* For the addon mock, we simply need an object that is able to emit events and has a
* status.
*/
function prepareMockAddon() {
const mockAddon = {
status: "unknown",
};
EventEmitter.decorate(mockAddon);
return mockAddon;
}
/**
* Prepare all mocks needed for the scanner tests.
*/
function prepareMocks() {
const mockScanner = prepareMockScanner();
const mockAddon = prepareMockAddon();
const addonAwareAdbScanner = new AddonAwareADBScanner(mockScanner, mockAddon);
return { addonAwareAdbScanner, mockAddon, mockScanner };
}
/**
* This test covers basic usage of enable() on the AddonAwareADBScanner, and checks the
* different behaviors based on the addon status.
*/
add_task(async function testCallingEnable() {
const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
// Check that enable() is not called if the addon is uninstalled
mockAddon.status = "uninstalled";
addonAwareAdbScanner.enable();
ok(mockScanner.enable.notCalled, "enable() was not called");
mockScanner.enable.reset();
// Check that enable() is called if the addon is installed
mockAddon.status = "installed";
addonAwareAdbScanner.enable();
ok(mockScanner.enable.called, "enable() was called");
mockScanner.enable.reset();
});
/**
* This test checks that enable()/disable() methods from the internal ADBScanner are
* called when the addon is installed or uninstalled.
*/
add_task(async function testUpdatingAddonEnablesDisablesScanner() {
const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
// Enable the addon aware scanner
addonAwareAdbScanner.enable();
ok(mockScanner.enable.notCalled, "enable() was not called initially");
// Check that enable() is called automatically when the addon is installed
mockAddon.status = "installed";
mockAddon.emit("update");
ok(mockScanner.enable.called, "enable() was called when installing the addon");
ok(mockScanner.disable.notCalled, "disable() was not called when installing the addon");
mockScanner.enable.reset();
mockScanner.disable.reset();
// Check that disabled() is called automatically when the addon is uninstalled
mockAddon.status = "uninstalled";
mockAddon.emit("update");
ok(mockScanner.enable.notCalled, "enable() was not called when uninstalling the addon");
ok(mockScanner.disable.called, "disable() was called when uninstalling the addon");
mockScanner.enable.reset();
mockScanner.disable.reset();
// Check that enable() is called again when the addon is reinstalled
mockAddon.status = "installed";
mockAddon.emit("update");
ok(mockScanner.enable.called, "enable() was called when installing the addon");
ok(mockScanner.disable.notCalled, "disable() was not called when installing the addon");
mockScanner.enable.reset();
mockScanner.disable.reset();
});
/**
* This test checks that disable() is forwarded from the AddonAwareADBScanner to the real
* scanner even if the addon is uninstalled. We might miss the addon uninstall
* notification, so it is safer to always proceed with disabling.
*/
add_task(async function testScannerIsDisabledWhenMissingAddonUpdate() {
const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
// Enable the addon aware scanner
mockAddon.status = "installed";
addonAwareAdbScanner.enable();
ok(mockScanner.enable.called, "enable() was called initially");
mockScanner.enable.reset();
// Uninstall the addon without firing any event
mockAddon.status = "uninstalled";
// Programmatically call disable, check that the scanner's disable is called even though
// the addon was uninstalled.
addonAwareAdbScanner.disable();
ok(mockScanner.disable.called, "disable() was called when uninstalling the addon");
mockScanner.disable.reset();
});
/**
* This test checks that when the AddonAwareADBScanner is disabled, then enable/disable
* are not called on the inner scanner when the addon status changes.
*/
add_task(async function testInnerEnableIsNotCalledIfNotStarted() {
const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
// Check that enable() is not called on the inner scanner when the addon is installed
// if the AddonAwareADBScanner was not enabled
mockAddon.status = "installed";
mockAddon.emit("update");
ok(mockScanner.enable.notCalled, "enable() was not called");
// Same for disable() and "uninstall"
mockAddon.status = "uninstalled";
mockAddon.emit("update");
ok(mockScanner.disable.notCalled, "disable() was not called");
// Enable the addon aware scanner
addonAwareAdbScanner.enable();
ok(mockScanner.enable.notCalled, "enable() was not called");
ok(mockScanner.disable.notCalled, "disable() was not called");
});
/**
* This test checks that when the AddonAwareADBScanner is disabled, installing the addon
* no longer enables the internal ADBScanner.
*/
add_task(async function testEnableIsNoLongerCalledAfterDisabling() {
const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
// Start with the addon installed
mockAddon.status = "installed";
addonAwareAdbScanner.enable();
ok(mockScanner.enable.called, "enable() was called since addon was already installed");
mockScanner.enable.reset();
// Here we call enable again to check that we will not add too many events.
// A single call to disable() should stop all listeners, even if we called enable()
// several times.
addonAwareAdbScanner.enable();
ok(mockScanner.enable.called, "enable() was called again");
mockScanner.enable.reset();
// Disable the scanner
addonAwareAdbScanner.disable();
ok(mockScanner.disable.called, "disable() was called");
mockScanner.disable.reset();
// Emit an addon update event
mockAddon.emit("update");
ok(mockScanner.enable.notCalled,
"enable() is not called since the main scanner is disabled");
});
/**
* Basic check that the "runtime-list-updated" event is forwarded.
*/
add_task(async function testListUpdatedEventForwarding() {
const { mockScanner, addonAwareAdbScanner } = prepareMocks();
const spy = sinon.spy();
addonAwareAdbScanner.on("runtime-list-updated", spy);
mockScanner.emit("runtime-list-updated");
ok(spy.called, "The runtime-list-updated event was forwarded from ADBScanner");
addonAwareAdbScanner.off("runtime-list-updated", spy);
});
/**
* Basic check that calls to scan() are forwarded.
*/
add_task(async function testScanCallForwarding() {
const { mockScanner, addonAwareAdbScanner } = prepareMocks();
ok(mockScanner.scan.notCalled, "ADBScanner scan() is not called initially");
addonAwareAdbScanner.scan();
mockScanner.emit("runtime-list-updated");
ok(mockScanner.scan.called, "ADBScanner scan() was called");
mockScanner.scan.reset();
});
/**
* Basic check that calls to scan() are forwarded.
*/
add_task(async function testListRuntimesCallForwarding() {
const { mockScanner, addonAwareAdbScanner } = prepareMocks();
ok(mockScanner.listRuntimes.notCalled,
"ADBScanner listRuntimes() is not called initially");
addonAwareAdbScanner.listRuntimes();
mockScanner.emit("runtime-list-updated");
ok(mockScanner.listRuntimes.called, "ADBScanner listRuntimes() was called");
mockScanner.scan.reset();
});

Просмотреть файл

@ -8,4 +8,3 @@ support-files =
[test_adb.js]
run-sequentially = An extension having the same id is installed/uninstalled in different tests
[test_addon-aware-adb-scanner.js]