mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 975591 - Part 1: Add device discovery service. r=paul
--HG-- rename : toolkit/devtools/moz.build => toolkit/devtools/discovery/moz.build rename : toolkit/devtools/moz.build => toolkit/devtools/discovery/tests/moz.build
This commit is contained in:
parent
650c4384dd
commit
eae2eaec8b
@ -638,6 +638,9 @@ pref("devtools.defaultColorUnit", "hex");
|
||||
// Used for devtools debugging
|
||||
pref("devtools.dump.emit", false);
|
||||
|
||||
// Disable device discovery logging
|
||||
pref("devtools.discovery.log", false);
|
||||
|
||||
// view source
|
||||
pref("view_source.syntax_highlight", true);
|
||||
pref("view_source.wrap_long_lines", false);
|
||||
|
@ -41,3 +41,4 @@
|
||||
[include:js/jsd/test/xpcshell.ini]
|
||||
[include:security/manager/ssl/tests/unit/xpcshell.ini]
|
||||
[include:toolkit/devtools/qrcode/tests/unit/xpcshell.ini]
|
||||
[include:toolkit/devtools/discovery/tests/unit/xpcshell.ini]
|
||||
|
@ -20,3 +20,4 @@
|
||||
[include:ipc/testshell/tests/xpcshell.ini]
|
||||
[include:b2g/components/test/unit/xpcshell.ini]
|
||||
[include:security/manager/ssl/tests/unit/xpcshell.ini]
|
||||
[include:toolkit/devtools/discovery/tests/unit/xpcshell.ini]
|
||||
|
399
toolkit/devtools/discovery/discovery.js
Normal file
399
toolkit/devtools/discovery/discovery.js
Normal file
@ -0,0 +1,399 @@
|
||||
/* 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";
|
||||
|
||||
/**
|
||||
* This implements a UDP mulitcast device discovery protocol that:
|
||||
* * Is optimized for mobile devices
|
||||
* * Doesn't require any special schema for service info
|
||||
*
|
||||
* To ensure it works well on mobile devices, there is no heartbeat or other
|
||||
* recurring transmission.
|
||||
*
|
||||
* Devices are typically in one of two groups: scanning for services or
|
||||
* providing services (though they may be in both groups as well).
|
||||
*
|
||||
* Scanning devices listen on UPDATE_PORT for UDP multicast traffic. When the
|
||||
* scanning device wants to force an update of the services available, it sends
|
||||
* a status packet to SCAN_PORT.
|
||||
*
|
||||
* Service provider devices listen on SCAN_PORT for any packets from scanning
|
||||
* devices. If one is recevied, the provider device sends a status packet
|
||||
* (listing the services it offers) to UPDATE_PORT.
|
||||
*
|
||||
* Scanning devices purge any previously known devices after REPLY_TIMEOUT ms
|
||||
* from that start of a scan if no reply is received during the most recent
|
||||
* scan.
|
||||
*
|
||||
* When a service is registered, is supplies a regular object with any details
|
||||
* about itself (a port number, for example) in a service-defined format, which
|
||||
* is then available to scanning devices.
|
||||
*/
|
||||
|
||||
const { Cu, CC, Cc, Ci } = require("chrome");
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
const { setTimeout, clearTimeout } = require("sdk/timers");
|
||||
|
||||
const UDPSocket = CC("@mozilla.org/network/udp-socket;1",
|
||||
"nsIUDPSocket",
|
||||
"init");
|
||||
|
||||
// TODO Bug 1027456: May need to reserve these with IANA
|
||||
const SCAN_PORT = 50624;
|
||||
const UPDATE_PORT = 50625;
|
||||
const ADDRESS = "224.0.0.200";
|
||||
const REPLY_TIMEOUT = 5000;
|
||||
|
||||
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
|
||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "converter", () => {
|
||||
let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
|
||||
createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
conv.charset = "utf8";
|
||||
return conv;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "sysInfo", () => {
|
||||
return Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
|
||||
Cu.import("resource://gre/modules/systemlibs.js");
|
||||
return libcutils;
|
||||
});
|
||||
|
||||
let logging = Services.prefs.getBoolPref("devtools.discovery.log");
|
||||
function log(msg) {
|
||||
if (logging) {
|
||||
console.log("DISCOVERY: " + msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Each Transport instance owns a single UDPSocket.
|
||||
* @param port integer
|
||||
* The port to listen on for incoming UDP multicast packets.
|
||||
*/
|
||||
function Transport(port) {
|
||||
EventEmitter.decorate(this);
|
||||
try {
|
||||
this.socket = new UDPSocket(port, false);
|
||||
this.socket.joinMulticast(ADDRESS);
|
||||
this.socket.asyncListen(this);
|
||||
} catch(e) {
|
||||
log("Failed to start new socket: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
Transport.prototype = {
|
||||
|
||||
/**
|
||||
* Send a object to some UDP port.
|
||||
* @param object object
|
||||
* Object which is the message to send
|
||||
* @param port integer
|
||||
* UDP port to send the message to
|
||||
*/
|
||||
send: function(object, port) {
|
||||
if (logging) {
|
||||
log("Send to " + port + ":\n" + JSON.stringify(object, null, 2));
|
||||
}
|
||||
let message = JSON.stringify(object);
|
||||
let rawMessage = converter.convertToByteArray(message);
|
||||
try {
|
||||
this.socket.send(ADDRESS, port, rawMessage, rawMessage.length);
|
||||
} catch(e) {
|
||||
log("Failed to send message: " + e);
|
||||
}
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.socket.close();
|
||||
},
|
||||
|
||||
// nsIUDPSocketListener
|
||||
|
||||
onPacketReceived: function(socket, message) {
|
||||
let messageData = message.data;
|
||||
let object = JSON.parse(messageData);
|
||||
object.from = message.fromAddr.address;
|
||||
let port = message.fromAddr.port;
|
||||
if (port == this.socket.port) {
|
||||
log("Ignoring looped message");
|
||||
return;
|
||||
}
|
||||
if (logging) {
|
||||
log("Recv on " + this.socket.port + ":\n" +
|
||||
JSON.stringify(object, null, 2));
|
||||
}
|
||||
this.emit("message", object);
|
||||
},
|
||||
|
||||
onStopListening: function() {}
|
||||
|
||||
};
|
||||
|
||||
function Discovery() {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this.localServices = {};
|
||||
this.remoteServices = {};
|
||||
this.device = { name: "unknown" };
|
||||
this.replyTimeout = REPLY_TIMEOUT;
|
||||
|
||||
// Defaulted to Transport, but can be altered by tests
|
||||
this._factories = { Transport: Transport };
|
||||
|
||||
this._transports = {
|
||||
scan: null,
|
||||
update: null
|
||||
};
|
||||
this._expectingReplies = {
|
||||
from: new Set()
|
||||
};
|
||||
|
||||
this._onRemoteScan = this._onRemoteScan.bind(this);
|
||||
this._onRemoteUpdate = this._onRemoteUpdate.bind(this);
|
||||
this._purgeMissingDevices = this._purgeMissingDevices.bind(this);
|
||||
|
||||
this._getSystemInfo();
|
||||
}
|
||||
|
||||
Discovery.prototype = {
|
||||
|
||||
/**
|
||||
* Add a new service offered by this device.
|
||||
* @param service string
|
||||
* Name of the service
|
||||
* @param info object
|
||||
* Arbitrary data about the service to announce to scanning devices
|
||||
*/
|
||||
addService: function(service, info) {
|
||||
log("ADDING LOCAL SERVICE");
|
||||
if (Object.keys(this.localServices).length === 0) {
|
||||
this._startListeningForScan();
|
||||
}
|
||||
this.localServices[service] = info;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a service offered by this device.
|
||||
* @param service string
|
||||
* Name of the service
|
||||
*/
|
||||
removeService: function(service) {
|
||||
delete this.localServices[service];
|
||||
if (Object.keys(this.localServices).length === 0) {
|
||||
this._stopListeningForScan();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Scan for service updates from other devices.
|
||||
*/
|
||||
scan: function() {
|
||||
this._startListeningForUpdate();
|
||||
this._waitForReplies();
|
||||
// TODO Bug 1027457: Use timer to debounce
|
||||
this._sendStatusTo(SCAN_PORT);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a list of all remote devices currently offering some service.:w
|
||||
*/
|
||||
getRemoteDevices: function() {
|
||||
let devices = new Set();
|
||||
for (let service in this.remoteServices) {
|
||||
for (let device in this.remoteServices[service]) {
|
||||
devices.add(device);
|
||||
}
|
||||
}
|
||||
return [...devices];
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a list of all remote devices currently offering a particular service.
|
||||
*/
|
||||
getRemoteDevicesWithService: function(service) {
|
||||
let devicesWithService = this.remoteServices[service] || {};
|
||||
return Object.keys(devicesWithService);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get service info (any details registered by the remote device) for a given
|
||||
* service on a device.
|
||||
*/
|
||||
getRemoteService: function(service, device) {
|
||||
let devicesWithService = this.remoteServices[service] || {};
|
||||
return devicesWithService[device];
|
||||
},
|
||||
|
||||
_waitForReplies: function() {
|
||||
clearTimeout(this._expectingReplies.timer);
|
||||
this._expectingReplies.from = new Set(this.getRemoteDevices());
|
||||
this._expectingReplies.timer =
|
||||
setTimeout(this._purgeMissingDevices, this.replyTimeout);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine a unique name to identify the current device.
|
||||
*/
|
||||
_getSystemInfo: function() {
|
||||
// TODO Bug 1027787: Uniquify device name somehow?
|
||||
try {
|
||||
if (Services.appinfo.widgetToolkit == "gonk") {
|
||||
this.device.name = libcutils.property_get("ro.product.device");
|
||||
} else {
|
||||
this.device.name = sysInfo.get("host");
|
||||
}
|
||||
log("Device: " + this.device.name);
|
||||
} catch(e) {
|
||||
log("Failed to get system info");
|
||||
this.device.name = "unknown";
|
||||
}
|
||||
},
|
||||
|
||||
get Transport() {
|
||||
return this._factories.Transport;
|
||||
},
|
||||
|
||||
_startListeningForScan: function() {
|
||||
if (this._transports.scan) {
|
||||
return; // Already listening
|
||||
}
|
||||
log("LISTEN FOR SCAN");
|
||||
this._transports.scan = new this.Transport(SCAN_PORT);
|
||||
this._transports.scan.on("message", this._onRemoteScan);
|
||||
},
|
||||
|
||||
_stopListeningForScan: function() {
|
||||
if (!this._transports.scan) {
|
||||
return; // Not listening
|
||||
}
|
||||
this._transports.scan.off("message", this._onRemoteScan);
|
||||
this._transports.scan.destroy();
|
||||
this._transports.scan = null;
|
||||
},
|
||||
|
||||
_startListeningForUpdate: function() {
|
||||
if (this._transports.update) {
|
||||
return; // Already listening
|
||||
}
|
||||
log("LISTEN FOR UPDATE");
|
||||
this._transports.update = new this.Transport(UPDATE_PORT);
|
||||
this._transports.update.on("message", this._onRemoteUpdate);
|
||||
},
|
||||
|
||||
_stopListeningForUpdate: function() {
|
||||
if (!this._transports.update) {
|
||||
return; // Not listening
|
||||
}
|
||||
this._transports.update.off("message", this._onRemoteUpdate);
|
||||
this._transports.update.destroy();
|
||||
this._transports.update = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* When sending message, we can use either transport, so just pick the first
|
||||
* one currently alive.
|
||||
*/
|
||||
get _outgoingTransport() {
|
||||
if (this._transports.scan) {
|
||||
return this._transports.scan;
|
||||
}
|
||||
if (this._transports.update) {
|
||||
return this._transports.update;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
_sendStatusTo: function(port) {
|
||||
let status = {
|
||||
device: this.device.name,
|
||||
services: this.localServices
|
||||
};
|
||||
this._outgoingTransport.send(status, port);
|
||||
},
|
||||
|
||||
_onRemoteScan: function() {
|
||||
// Send my own status in response
|
||||
log("GOT SCAN REQUEST");
|
||||
this._sendStatusTo(UPDATE_PORT);
|
||||
},
|
||||
|
||||
_onRemoteUpdate: function(e, update) {
|
||||
log("GOT REMOTE UPDATE");
|
||||
|
||||
let remoteDevice = update.device;
|
||||
let remoteHost = update.from;
|
||||
|
||||
// First, loop over the known services
|
||||
for (let service in this.remoteServices) {
|
||||
let devicesWithService = this.remoteServices[service];
|
||||
let hadServiceForDevice = !!devicesWithService[remoteDevice];
|
||||
let haveServiceForDevice = service in update.services;
|
||||
// If the remote device used to have service, but doesn't any longer, then
|
||||
// it was deleted, so we remove it here.
|
||||
if (hadServiceForDevice && !haveServiceForDevice) {
|
||||
delete devicesWithService[remoteDevice];
|
||||
log("REMOVED " + service + ", DEVICE " + remoteDevice);
|
||||
this.emit(service + "-device-removed", remoteDevice);
|
||||
}
|
||||
}
|
||||
|
||||
// Second, loop over the services in the received update
|
||||
for (let service in update.services) {
|
||||
// Detect if this is a new device for this service
|
||||
let newDevice = !this.remoteServices[service] ||
|
||||
!this.remoteServices[service][remoteDevice];
|
||||
|
||||
// Look up the service info we may have received previously from the same
|
||||
// remote device
|
||||
let devicesWithService = this.remoteServices[service] || {};
|
||||
let oldDeviceInfo = devicesWithService[remoteDevice];
|
||||
|
||||
// Store the service info from the remote device
|
||||
let newDeviceInfo = Cu.cloneInto(update.services[service], {});
|
||||
newDeviceInfo.host = remoteHost;
|
||||
devicesWithService[remoteDevice] = newDeviceInfo;
|
||||
this.remoteServices[service] = devicesWithService;
|
||||
|
||||
// If this is a new service for the remote device, announce the addition
|
||||
if (newDevice) {
|
||||
log("ADDED " + service + ", DEVICE " + remoteDevice);
|
||||
this.emit(service + "-device-added", remoteDevice, newDeviceInfo);
|
||||
}
|
||||
|
||||
// If we've seen this service from the remote device, but the details have
|
||||
// changed, announce the update
|
||||
if (!newDevice &&
|
||||
JSON.stringify(oldDeviceInfo) != JSON.stringify(newDeviceInfo)) {
|
||||
log("UPDATED " + service + ", DEVICE " + remoteDevice);
|
||||
this.emit(service + "-device-updated", remoteDevice, newDeviceInfo);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_purgeMissingDevices: function() {
|
||||
log("PURGING MISSING DEVICES");
|
||||
for (let service in this.remoteServices) {
|
||||
let devicesWithService = this.remoteServices[service];
|
||||
for (let remoteDevice in devicesWithService) {
|
||||
// If we're still expecting a reply from a remote device when it's time
|
||||
// to purge, then the service is removed.
|
||||
if (this._expectingReplies.from.has(remoteDevice)) {
|
||||
delete devicesWithService[remoteDevice];
|
||||
log("REMOVED " + service + ", DEVICE " + remoteDevice);
|
||||
this.emit(service + "-device-removed", remoteDevice);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
let discovery = new Discovery();
|
||||
|
||||
module.exports = discovery;
|
13
toolkit/devtools/discovery/moz.build
Normal file
13
toolkit/devtools/discovery/moz.build
Normal file
@ -0,0 +1,13 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
TEST_DIRS += ['tests']
|
||||
|
||||
JS_MODULES_PATH = 'modules/devtools/discovery'
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'discovery.js',
|
||||
]
|
7
toolkit/devtools/discovery/tests/moz.build
Normal file
7
toolkit/devtools/discovery/tests/moz.build
Normal file
@ -0,0 +1,7 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
|
139
toolkit/devtools/discovery/tests/unit/test_discovery.js
Normal file
139
toolkit/devtools/discovery/tests/unit/test_discovery.js
Normal file
@ -0,0 +1,139 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
|
||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
|
||||
Services.prefs.setBoolPref("devtools.discovery.log", true);
|
||||
|
||||
do_register_cleanup(() => {
|
||||
Services.prefs.clearUserPref("devtools.discovery.log");
|
||||
});
|
||||
|
||||
const { devtools } =
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const { Promise: promise } =
|
||||
Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
const { require } = devtools;
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
const discovery = require("devtools/toolkit/discovery/discovery");
|
||||
const { setTimeout, clearTimeout } = require("sdk/timers");
|
||||
|
||||
function log(msg) {
|
||||
do_print("DISCOVERY: " + msg);
|
||||
}
|
||||
|
||||
// Global map of actively listening ports to TestTransport instances
|
||||
let gTestTransports = {};
|
||||
|
||||
/**
|
||||
* Implements the same API as Transport in discovery.js. Here, no UDP sockets
|
||||
* are used. Instead, messages are delivered immediately.
|
||||
*/
|
||||
function TestTransport(port) {
|
||||
EventEmitter.decorate(this);
|
||||
this.port = port;
|
||||
gTestTransports[this.port] = this;
|
||||
}
|
||||
|
||||
TestTransport.prototype = {
|
||||
|
||||
send: function(object, port) {
|
||||
log("Send to " + port + ":\n" + JSON.stringify(object, null, 2));
|
||||
if (!gTestTransports[port]) {
|
||||
log("No listener on port " + port);
|
||||
return;
|
||||
}
|
||||
let message = JSON.stringify(object);
|
||||
gTestTransports[port].onPacketReceived(null, message);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
delete gTestTransports[this.port];
|
||||
},
|
||||
|
||||
// nsIUDPSocketListener
|
||||
|
||||
onPacketReceived: function(socket, message) {
|
||||
let object = JSON.parse(message);
|
||||
object.from = "localhost";
|
||||
log("Recv on " + this.port + ":\n" + JSON.stringify(object, null, 2));
|
||||
this.emit("message", object);
|
||||
},
|
||||
|
||||
onStopListening: function(socket, status) {}
|
||||
|
||||
};
|
||||
|
||||
// Use TestTransport instead of the usual Transport
|
||||
discovery._factories.Transport = TestTransport;
|
||||
discovery.device.name = "test-device";
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function*() {
|
||||
// At startup, no remote devices are known
|
||||
deepEqual(discovery.getRemoteDevicesWithService("devtools"), []);
|
||||
deepEqual(discovery.getRemoteDevicesWithService("penguins"), []);
|
||||
|
||||
discovery.scan();
|
||||
|
||||
// No services added yet, still empty
|
||||
deepEqual(discovery.getRemoteDevicesWithService("devtools"), []);
|
||||
deepEqual(discovery.getRemoteDevicesWithService("penguins"), []);
|
||||
|
||||
discovery.addService("devtools", { port: 1234 });
|
||||
|
||||
// Changes not visible until next scan
|
||||
deepEqual(discovery.getRemoteDevicesWithService("devtools"), []);
|
||||
deepEqual(discovery.getRemoteDevicesWithService("penguins"), []);
|
||||
|
||||
yield scanForChange("devtools", "added");
|
||||
|
||||
// Now we see the new service
|
||||
deepEqual(discovery.getRemoteDevicesWithService("devtools"), ["test-device"]);
|
||||
deepEqual(discovery.getRemoteDevicesWithService("penguins"), []);
|
||||
|
||||
discovery.addService("penguins", { tux: true });
|
||||
yield scanForChange("penguins", "added");
|
||||
|
||||
deepEqual(discovery.getRemoteDevicesWithService("devtools"), ["test-device"]);
|
||||
deepEqual(discovery.getRemoteDevicesWithService("penguins"), ["test-device"]);
|
||||
deepEqual(discovery.getRemoteDevices(), ["test-device"]);
|
||||
|
||||
deepEqual(discovery.getRemoteService("devtools", "test-device"),
|
||||
{ port: 1234, host: "localhost" });
|
||||
deepEqual(discovery.getRemoteService("penguins", "test-device"),
|
||||
{ tux: true, host: "localhost" });
|
||||
|
||||
discovery.removeService("devtools");
|
||||
yield scanForChange("devtools", "removed");
|
||||
|
||||
discovery.addService("penguins", { tux: false });
|
||||
yield scanForChange("penguins", "updated");
|
||||
|
||||
// Split the scanning side from the service side to simulate the machine with
|
||||
// the service becoming unreachable
|
||||
gTestTransports = {};
|
||||
|
||||
discovery.removeService("penguins");
|
||||
yield scanForChange("penguins", "removed");
|
||||
});
|
||||
|
||||
function scanForChange(service, changeType) {
|
||||
let deferred = promise.defer();
|
||||
let timer = setTimeout(() => {
|
||||
deferred.reject(new Error("Reply never arrived"));
|
||||
}, discovery.replyTimeout + 500);
|
||||
discovery.on(service + "-device-" + changeType, function onChange() {
|
||||
discovery.off(service + "-device-" + changeType, onChange);
|
||||
clearTimeout(timer);
|
||||
deferred.resolve();
|
||||
});
|
||||
discovery.scan();
|
||||
return deferred.promise;
|
||||
}
|
5
toolkit/devtools/discovery/tests/unit/xpcshell.ini
Normal file
5
toolkit/devtools/discovery/tests/unit/xpcshell.ini
Normal file
@ -0,0 +1,5 @@
|
||||
[DEFAULT]
|
||||
head =
|
||||
tail =
|
||||
|
||||
[test_discovery.js]
|
@ -17,6 +17,7 @@ PARALLEL_DIRS += [
|
||||
'qrcode',
|
||||
'transport',
|
||||
'tern',
|
||||
'discovery'
|
||||
]
|
||||
|
||||
MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
|
||||
|
Loading…
Reference in New Issue
Block a user