mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
merge fx-team to mozilla-central a=merge
This commit is contained in:
commit
55495c33fa
@ -472,13 +472,6 @@ SettingsListener.observe('debugger.remote-mode', false, function(value) {
|
||||
#endif
|
||||
});
|
||||
|
||||
// If debug access to certified apps is allowed, we need to preserve system
|
||||
// sources so that they are visible in the debugger.
|
||||
let forbidCertified =
|
||||
Services.prefs.getBoolPref('devtools.debugger.forbid-certified-apps');
|
||||
Services.prefs.setBoolPref('javascript.options.discardSystemSource',
|
||||
forbidCertified);
|
||||
|
||||
// =================== Device Storage ====================
|
||||
SettingsListener.observe('device.storage.writable.name', 'sdcard', function(value) {
|
||||
if (Services.prefs.getPrefType('device.storage.writable.name') != Ci.nsIPrefBranch.PREF_STRING) {
|
||||
|
@ -33,13 +33,11 @@ let test = asyncTest(function*() {
|
||||
* Just check current page
|
||||
*/
|
||||
function* navigate(usage, options) {
|
||||
let running = yield usage._testOnly_isRunning();
|
||||
ok(!running, "csscoverage not is running");
|
||||
ok(!usage.isRunning(), "csscoverage is not running");
|
||||
|
||||
yield usage.oneshot();
|
||||
|
||||
running = yield usage._testOnly_isRunning();
|
||||
ok(!running, "csscoverage not is running");
|
||||
ok(!usage.isRunning(), "csscoverage is still not running");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,8 +35,7 @@ let test = asyncTest(function*() {
|
||||
function* navigate(usage, options) {
|
||||
yield usage.start();
|
||||
|
||||
let running = yield usage._testOnly_isRunning();
|
||||
ok(running, "csscoverage is running");
|
||||
ok(usage.isRunning(), "csscoverage is running");
|
||||
|
||||
yield helpers.navigate(PAGE_1, options);
|
||||
|
||||
@ -49,8 +48,7 @@ function* navigate(usage, options) {
|
||||
|
||||
yield usage.stop();
|
||||
|
||||
running = yield usage._testOnly_isRunning();
|
||||
ok(!running, "csscoverage not is running");
|
||||
ok(!usage.isRunning(), "csscoverage not is running");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -4,18 +4,19 @@
|
||||
|
||||
(function() {
|
||||
const DEVTOOLS_SKIN_URL = "chrome://browser/skin/devtools/";
|
||||
let documentElement = document.documentElement;
|
||||
|
||||
function forceStyle() {
|
||||
let computedStyle = window.getComputedStyle(document.documentElement);
|
||||
let computedStyle = window.getComputedStyle(documentElement);
|
||||
if (!computedStyle) {
|
||||
// Null when documentElement is not ready. This method is anyways not
|
||||
// required then as scrollbars would be in their state without flushing.
|
||||
return;
|
||||
}
|
||||
let display = computedStyle.display; // Save display value
|
||||
document.documentElement.style.display = "none";
|
||||
window.getComputedStyle(document.documentElement).display; // Flush
|
||||
document.documentElement.style.display = display; // Restore
|
||||
documentElement.style.display = "none";
|
||||
window.getComputedStyle(documentElement).display; // Flush
|
||||
documentElement.style.display = display; // Restore
|
||||
}
|
||||
|
||||
function switchTheme(newTheme, oldTheme) {
|
||||
@ -61,8 +62,8 @@
|
||||
forceStyle();
|
||||
}
|
||||
|
||||
document.documentElement.classList.remove("theme-" + oldTheme);
|
||||
document.documentElement.classList.add("theme-" + newTheme);
|
||||
documentElement.classList.remove("theme-" + oldTheme);
|
||||
documentElement.classList.add("theme-" + newTheme);
|
||||
}
|
||||
|
||||
function handlePrefChange(event, data) {
|
||||
@ -78,11 +79,14 @@
|
||||
const {devtools} = Components.utils.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const StylesheetUtils = devtools.require("sdk/stylesheet/utils");
|
||||
|
||||
let theme = Services.prefs.getCharPref("devtools.theme");
|
||||
switchTheme(theme);
|
||||
if (documentElement.hasAttribute("force-theme")) {
|
||||
switchTheme(documentElement.getAttribute("force-theme"));
|
||||
} else {
|
||||
switchTheme(Services.prefs.getCharPref("devtools.theme"));
|
||||
|
||||
gDevTools.on("pref-changed", handlePrefChange);
|
||||
window.addEventListener("unload", function() {
|
||||
gDevTools.off("pref-changed", handlePrefChange);
|
||||
});
|
||||
gDevTools.on("pref-changed", handlePrefChange);
|
||||
window.addEventListener("unload", function() {
|
||||
gDevTools.off("pref-changed", handlePrefChange);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
@ -157,6 +157,7 @@ function Editor(config) {
|
||||
autoCloseBrackets: "()[]{}''\"\"",
|
||||
autoCloseEnabled: useAutoClose,
|
||||
theme: "mozilla",
|
||||
themeSwitching: true,
|
||||
autocomplete: false
|
||||
};
|
||||
|
||||
@ -258,6 +259,9 @@ Editor.prototype = {
|
||||
env.removeEventListener("load", onLoad, true);
|
||||
let win = env.contentWindow.wrappedJSObject;
|
||||
|
||||
if (!this.config.themeSwitching)
|
||||
win.document.documentElement.setAttribute("force-theme", "light");
|
||||
|
||||
CM_SCRIPTS.forEach((url) =>
|
||||
Services.scriptloader.loadSubScript(url, win, "utf8"));
|
||||
|
||||
|
@ -2869,7 +2869,13 @@ function getParentTextProperty(node) {
|
||||
if (!parent) {
|
||||
return null;
|
||||
}
|
||||
return parent.querySelector(".ruleview-propertyvalue").textProperty;
|
||||
|
||||
let propValue = parent.querySelector(".ruleview-propertyvalue");
|
||||
if (!propValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return propValue.textProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -270,6 +270,7 @@ run-if = os == "mac"
|
||||
[browser_webconsole_expandable_timestamps.js]
|
||||
[browser_webconsole_autocomplete_in_debugger_stackframe.js]
|
||||
[browser_webconsole_autocomplete_popup_close_on_tab_switch.js]
|
||||
[browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js]
|
||||
[browser_console_hide_jsterm_when_devtools_chrome_enabled_false.js]
|
||||
[browser_webconsole_output_01.js]
|
||||
[browser_webconsole_output_02.js]
|
||||
|
@ -0,0 +1,42 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that properties starting with underscores or dollars can be
|
||||
// autocompleted (bug 967468).
|
||||
|
||||
function test() {
|
||||
const TEST_URI = "data:text/html;charset=utf8,test autocompletion with $ or _";
|
||||
Task.spawn(runner).then(finishTest);
|
||||
|
||||
function* runner() {
|
||||
function autocomplete(term) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
jsterm.setInputValue(term);
|
||||
jsterm.complete(jsterm.COMPLETE_HINT_ONLY, deferred.resolve);
|
||||
|
||||
yield deferred.promise;
|
||||
|
||||
ok(popup.itemCount > 0, "There's suggestions for '" + term + "'");
|
||||
}
|
||||
|
||||
yield addTab(TEST_URI);
|
||||
let { jsterm } = yield openConsole(tab);
|
||||
let popup = jsterm.autocompletePopup;
|
||||
|
||||
jsterm.execute("let testObject = {$$aaab: '', $$aaac: ''}");
|
||||
|
||||
// Should work with bug 967468.
|
||||
yield autocomplete("Object.__d");
|
||||
yield autocomplete("testObject.$$a");
|
||||
|
||||
// Here's when things go wrong in bug 967468.
|
||||
yield autocomplete("Object.__de");
|
||||
yield autocomplete("testObject.$$aa");
|
||||
}
|
||||
}
|
@ -4292,8 +4292,8 @@ JSTerm.prototype = {
|
||||
|
||||
if (this._autocompleteQuery && input.startsWith(this._autocompleteQuery)) {
|
||||
let filterBy = input;
|
||||
// Find the last non-alphanumeric if exists.
|
||||
let lastNonAlpha = input.match(/[^a-zA-Z0-9][a-zA-Z0-9]*$/);
|
||||
// Find the last non-alphanumeric other than _ or $ if it exists.
|
||||
let lastNonAlpha = input.match(/[^a-zA-Z0-9_$][a-zA-Z0-9_$]*$/);
|
||||
// If input contains non-alphanumerics, use the part after the last one
|
||||
// to filter the cache
|
||||
if (lastNonAlpha) {
|
||||
|
@ -233,7 +233,15 @@ let UI = {
|
||||
/********** RUNTIME **********/
|
||||
|
||||
updateRuntimeList: function() {
|
||||
let wifiHeaderNode = document.querySelector("#runtime-header-wifi-devices");
|
||||
if (AppManager.isWiFiScanningEnabled) {
|
||||
wifiHeaderNode.removeAttribute("hidden");
|
||||
} else {
|
||||
wifiHeaderNode.setAttribute("hidden", "true");
|
||||
}
|
||||
|
||||
let USBListNode = document.querySelector("#runtime-panel-usbruntime");
|
||||
let WiFiListNode = document.querySelector("#runtime-panel-wifi-devices");
|
||||
let simulatorListNode = document.querySelector("#runtime-panel-simulators");
|
||||
let customListNode = document.querySelector("#runtime-panel-custom");
|
||||
|
||||
@ -261,6 +269,7 @@ let UI = {
|
||||
|
||||
for (let [type, parent] of [
|
||||
["usb", USBListNode],
|
||||
["wifi", WiFiListNode],
|
||||
["simulator", simulatorListNode],
|
||||
["custom", customListNode],
|
||||
]) {
|
||||
@ -775,6 +784,8 @@ let Cmds = {
|
||||
},
|
||||
|
||||
showRuntimePanel: function() {
|
||||
AppManager.scanForWiFiRuntimes();
|
||||
|
||||
let panel = document.querySelector("#runtime-panel");
|
||||
let anchor = document.querySelector("#runtime-panel-button > .panel-button-anchor");
|
||||
|
||||
|
@ -144,6 +144,8 @@
|
||||
<toolbarbutton class="panel-item-help" label="&runtimePanel_nousbdevice;" id="runtime-panel-nousbdevice" command="cmd_showTroubleShooting"/>
|
||||
<toolbarbutton class="panel-item-help" label="&runtimePanel_noadbhelper;" id="runtime-panel-noadbhelper" command="cmd_showAddons"/>
|
||||
<vbox id="runtime-panel-usbruntime"></vbox>
|
||||
<label class="panel-header" id="runtime-header-wifi-devices">&runtimePanel_WiFiDevices;</label>
|
||||
<vbox id="runtime-panel-wifi-devices"></vbox>
|
||||
<label class="panel-header">&runtimePanel_simulators;</label>
|
||||
<toolbarbutton class="panel-item-help" label="&runtimePanel_nosimulator;" id="runtime-panel-nosimulator" command="cmd_showAddons"/>
|
||||
<vbox id="runtime-panel-simulators"></vbox>
|
||||
|
@ -59,6 +59,7 @@
|
||||
<!ENTITY projectPanel_myProjects "My Projects">
|
||||
<!ENTITY projectPanel_runtimeApps "Runtime Apps">
|
||||
<!ENTITY runtimePanel_USBDevices "USB Devices">
|
||||
<!ENTITY runtimePanel_WiFiDevices "WiFi Devices">
|
||||
<!ENTITY runtimePanel_simulators "Simulators">
|
||||
<!ENTITY runtimePanel_custom "Custom">
|
||||
<!ENTITY runtimePanel_nosimulator "Install Simulator">
|
||||
|
@ -20,10 +20,14 @@ const AppActorFront = require("devtools/app-actor-front");
|
||||
const {getDeviceFront} = require("devtools/server/actors/device");
|
||||
const {setTimeout} = require("sdk/timers");
|
||||
const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
const {USBRuntime, SimulatorRuntime, gLocalRuntime, gRemoteRuntime} = require("devtools/webide/runtimes");
|
||||
const {USBRuntime, WiFiRuntime, SimulatorRuntime,
|
||||
gLocalRuntime, gRemoteRuntime} = require("devtools/webide/runtimes");
|
||||
const discovery = require("devtools/toolkit/discovery/discovery");
|
||||
|
||||
const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
|
||||
|
||||
const WIFI_SCANNING_PREF = "devtools.remote.wifi.scan";
|
||||
|
||||
exports.AppManager = AppManager = {
|
||||
|
||||
// FIXME: will break when devtools/app-manager will be removed:
|
||||
@ -42,12 +46,21 @@ exports.AppManager = AppManager = {
|
||||
this.webAppsStore = new WebappsStore(this.connection);
|
||||
this.webAppsStore.on("store-ready", this.onWebAppsStoreready);
|
||||
|
||||
this.runtimeList = {usb: [], simulator: [], custom: [gRemoteRuntime]};
|
||||
this.runtimeList = {
|
||||
usb: [],
|
||||
wifi: [],
|
||||
simulator: [],
|
||||
custom: [gRemoteRuntime]
|
||||
};
|
||||
if (Services.prefs.getBoolPref("devtools.webide.enableLocalRuntime")) {
|
||||
this.runtimeList.custom.push(gLocalRuntime);
|
||||
}
|
||||
this.trackUSBRuntimes();
|
||||
this.trackWiFiRuntimes();
|
||||
this.trackSimulatorRuntimes();
|
||||
|
||||
this.observe = this.observe.bind(this);
|
||||
Services.prefs.addObserver(WIFI_SCANNING_PREF, this, false);
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
@ -55,6 +68,7 @@ exports.AppManager = AppManager = {
|
||||
this.selectedProject = null;
|
||||
this.selectedRuntime = null;
|
||||
this.untrackUSBRuntimes();
|
||||
this.untrackWiFiRuntimes();
|
||||
this.untrackSimulatorRuntimes();
|
||||
this._runningApps.clear();
|
||||
this.runtimeList = null;
|
||||
@ -65,6 +79,17 @@ exports.AppManager = AppManager = {
|
||||
this._listTabsResponse = null;
|
||||
this.connection.disconnect();
|
||||
this.connection = null;
|
||||
Services.prefs.removeObserver(WIFI_SCANNING_PREF, this);
|
||||
},
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
if (data !== WIFI_SCANNING_PREF) {
|
||||
return;
|
||||
}
|
||||
// Cycle WiFi tracking to reflect the new value
|
||||
this.untrackWiFiRuntimes();
|
||||
this.trackWiFiRuntimes();
|
||||
this._updateWiFiRuntimes();
|
||||
},
|
||||
|
||||
update: function(what, details) {
|
||||
@ -505,6 +530,40 @@ exports.AppManager = AppManager = {
|
||||
this.update("runtimelist");
|
||||
},
|
||||
|
||||
get isWiFiScanningEnabled() {
|
||||
return Services.prefs.getBoolPref(WIFI_SCANNING_PREF);
|
||||
},
|
||||
scanForWiFiRuntimes: function() {
|
||||
if (!this.isWiFiScanningEnabled) {
|
||||
return;
|
||||
}
|
||||
discovery.scan();
|
||||
},
|
||||
trackWiFiRuntimes: function() {
|
||||
if (!this.isWiFiScanningEnabled) {
|
||||
return;
|
||||
}
|
||||
this._updateWiFiRuntimes = this._updateWiFiRuntimes.bind(this);
|
||||
discovery.on("devtools-device-added", this._updateWiFiRuntimes);
|
||||
discovery.on("devtools-device-updated", this._updateWiFiRuntimes);
|
||||
discovery.on("devtools-device-removed", this._updateWiFiRuntimes);
|
||||
},
|
||||
untrackWiFiRuntimes: function() {
|
||||
if (!this.isWiFiScanningEnabled) {
|
||||
return;
|
||||
}
|
||||
discovery.off("devtools-device-added", this._updateWiFiRuntimes);
|
||||
discovery.off("devtools-device-updated", this._updateWiFiRuntimes);
|
||||
discovery.off("devtools-device-removed", this._updateWiFiRuntimes);
|
||||
},
|
||||
_updateWiFiRuntimes: function() {
|
||||
this.runtimeList.wifi = [];
|
||||
for (let device of discovery.getRemoteDevicesWithService("devtools")) {
|
||||
this.runtimeList.wifi.push(new WiFiRuntime(device));
|
||||
}
|
||||
this.update("runtimelist");
|
||||
},
|
||||
|
||||
trackSimulatorRuntimes: function() {
|
||||
this._updateSimulatorRuntimes = this._updateSimulatorRuntimes.bind(this);
|
||||
Simulator.on("register", this._updateSimulatorRuntimes);
|
||||
|
@ -8,6 +8,7 @@ const {Services} = Cu.import("resource://gre/modules/Services.jsm");
|
||||
const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
|
||||
const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
|
||||
const {DebuggerServer} = require("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
const discovery = require("devtools/toolkit/discovery/discovery");
|
||||
|
||||
const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
|
||||
|
||||
@ -19,7 +20,7 @@ USBRuntime.prototype = {
|
||||
connect: function(connection) {
|
||||
let device = Devices.getByName(this.id);
|
||||
if (!device) {
|
||||
return promise.reject("Can't find device: " + id);
|
||||
return promise.reject("Can't find device: " + this.getName());
|
||||
}
|
||||
return device.connect().then((port) => {
|
||||
connection.host = "localhost";
|
||||
@ -35,6 +36,29 @@ USBRuntime.prototype = {
|
||||
},
|
||||
}
|
||||
|
||||
function WiFiRuntime(deviceName) {
|
||||
this.deviceName = deviceName;
|
||||
}
|
||||
|
||||
WiFiRuntime.prototype = {
|
||||
connect: function(connection) {
|
||||
let service = discovery.getRemoteService("devtools", this.deviceName);
|
||||
if (!service) {
|
||||
return promise.reject("Can't find device: " + this.getName());
|
||||
}
|
||||
connection.host = service.host;
|
||||
connection.port = service.port;
|
||||
connection.connect();
|
||||
return promise.resolve();
|
||||
},
|
||||
getID: function() {
|
||||
return this.deviceName;
|
||||
},
|
||||
getName: function() {
|
||||
return this.deviceName;
|
||||
},
|
||||
}
|
||||
|
||||
function SimulatorRuntime(version) {
|
||||
this.version = version;
|
||||
}
|
||||
@ -44,7 +68,7 @@ SimulatorRuntime.prototype = {
|
||||
let port = ConnectionManager.getFreeTCPPort();
|
||||
let simulator = Simulator.getByVersion(this.version);
|
||||
if (!simulator || !simulator.launch) {
|
||||
return promise.reject("Can't find simulator: " + this.version);
|
||||
return promise.reject("Can't find simulator: " + this.getName());
|
||||
}
|
||||
return simulator.launch({port: port}).then(() => {
|
||||
connection.port = port;
|
||||
@ -102,6 +126,7 @@ let gRemoteRuntime = {
|
||||
}
|
||||
|
||||
exports.USBRuntime = USBRuntime;
|
||||
exports.WiFiRuntime = WiFiRuntime;
|
||||
exports.SimulatorRuntime = SimulatorRuntime;
|
||||
exports.gRemoteRuntime = gRemoteRuntime;
|
||||
exports.gLocalRuntime = gLocalRuntime;
|
||||
|
@ -187,6 +187,7 @@ panel > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
#runtime-permissions,
|
||||
#runtime-screenshot,
|
||||
.runtime-panel-item-usb,
|
||||
.runtime-panel-item-wifi,
|
||||
.runtime-panel-item-custom,
|
||||
.runtime-panel-item-simulator {
|
||||
list-style-image: url("icons.png");
|
||||
@ -195,6 +196,7 @@ panel > .panel-arrowcontainer > .panel-arrowcontent {
|
||||
#runtime-screenshot { -moz-image-region: rect(200px, 640px, 240px, 600px) }
|
||||
#runtime-permissions { -moz-image-region: rect(100px, 840px, 140px, 800px) }
|
||||
.runtime-panel-item-usb { -moz-image-region: rect(100px, 640px, 140px, 600px) }
|
||||
.runtime-panel-item-wifi { -moz-image-region: rect(100px, 640px, 140px, 600px) }
|
||||
.runtime-panel-item-custom { -moz-image-region: rect(100px, 640px, 140px, 600px) }
|
||||
.runtime-panel-item-simulator { -moz-image-region: rect(100px, 740px, 140px, 700px) }
|
||||
|
||||
|
@ -27,8 +27,8 @@ public class GlobalConstants {
|
||||
|
||||
public static final String MOZ_APP_DISPLAYNAME = "@MOZ_APP_DISPLAYNAME@";
|
||||
public static final String MOZ_APP_VERSION = "@MOZ_APP_VERSION@";
|
||||
public static final String BROWSER_INTENT_PACKAGE = "org.mozilla.gecko";
|
||||
public static final String BROWSER_INTENT_CLASS = BROWSER_INTENT_PACKAGE + ".BrowserApp";
|
||||
public static final String BROWSER_INTENT_PACKAGE = "@ANDROID_PACKAGE_NAME@";
|
||||
public static final String BROWSER_INTENT_CLASS = "org.mozilla.gecko.BrowserApp";
|
||||
|
||||
/**
|
||||
* Bug 800244: this signing-level permission protects broadcast intents that
|
||||
|
@ -40,10 +40,10 @@ TabSource.prototype = {
|
||||
let label;
|
||||
if (tab.browser.contentTitle)
|
||||
label = tab.browser.contentTitle;
|
||||
else if (tab.browser.contentURI && tab.browser.contentURI.spec)
|
||||
else if (tab.browser.contentURI)
|
||||
label = tab.browser.contentURI.spec;
|
||||
else
|
||||
label = tab.originalURI;
|
||||
label = tab.originalURI.spec;
|
||||
return { label: label,
|
||||
icon: "thumbnail:" + tab.id }
|
||||
}));
|
||||
|
@ -638,6 +638,11 @@ pref("devtools.defaultColorUnit", "hex");
|
||||
// Used for devtools debugging
|
||||
pref("devtools.dump.emit", false);
|
||||
|
||||
// Disable device discovery logging
|
||||
pref("devtools.discovery.log", false);
|
||||
// Disable scanning for DevTools devices via WiFi
|
||||
pref("devtools.remote.wifi.scan", 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]
|
||||
|
@ -5,6 +5,7 @@ const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
// XXX: bug 912476 make this module a real protocol.js front
|
||||
// by converting webapps actor to protocol.js
|
||||
@ -18,6 +19,9 @@ const CHUNK_SIZE = 10000;
|
||||
|
||||
const appTargets = new Map();
|
||||
|
||||
const AppActorFront = exports;
|
||||
EventEmitter.decorate(AppActorFront);
|
||||
|
||||
function addDirToZip(writer, dir, basePath) {
|
||||
let files = dir.directoryEntries;
|
||||
|
||||
@ -102,15 +106,34 @@ function uploadPackageJSON(client, webappsActor, packageFile) {
|
||||
openFile(res.actor);
|
||||
});
|
||||
|
||||
let fileSize;
|
||||
let bytesRead = 0;
|
||||
|
||||
function emitProgress() {
|
||||
emitInstallProgress({
|
||||
bytesSent: bytesRead,
|
||||
totalBytes: fileSize
|
||||
});
|
||||
}
|
||||
|
||||
function openFile(actor) {
|
||||
let openedFile;
|
||||
OS.File.open(packageFile.path)
|
||||
.then(function (file) {
|
||||
uploadChunk(actor, file);
|
||||
.then(file => {
|
||||
openedFile = file;
|
||||
return openedFile.stat();
|
||||
})
|
||||
.then(fileInfo => {
|
||||
fileSize = fileInfo.size;
|
||||
emitProgress();
|
||||
uploadChunk(actor, openedFile);
|
||||
});
|
||||
}
|
||||
function uploadChunk(actor, file) {
|
||||
file.read(CHUNK_SIZE)
|
||||
.then(function (bytes) {
|
||||
bytesRead += bytes.length;
|
||||
emitProgress();
|
||||
// To work around the fact that JSON.stringify translates the typed
|
||||
// array to object, we are encoding the typed array here into a string
|
||||
let chunk = String.fromCharCode.apply(null, bytes);
|
||||
@ -168,7 +191,11 @@ function uploadPackageBulk(client, webappsActor, packageFile) {
|
||||
|
||||
request.on("bulk-send-ready", ({copyFrom}) => {
|
||||
NetUtil.asyncFetch(packageFile, function(inputStream) {
|
||||
copyFrom(inputStream).then(() => {
|
||||
let copying = copyFrom(inputStream);
|
||||
copying.on("progress", (e, progress) => {
|
||||
emitInstallProgress(progress);
|
||||
});
|
||||
copying.then(() => {
|
||||
console.log("Bulk upload done");
|
||||
inputStream.close();
|
||||
deferred.resolve(actor);
|
||||
@ -236,6 +263,16 @@ function installPackaged(client, webappsActor, packagePath, appId) {
|
||||
}
|
||||
exports.installPackaged = installPackaged;
|
||||
|
||||
/**
|
||||
* Emits numerous events as packaged app installation proceeds.
|
||||
* The progress object contains:
|
||||
* * bytesSent: The number of bytes sent so far
|
||||
* * totalBytes: The total number of bytes to send
|
||||
*/
|
||||
function emitInstallProgress(progress) {
|
||||
AppActorFront.emit("install-progress", progress);
|
||||
}
|
||||
|
||||
function installHosted(client, webappsActor, appId, metadata, manifest) {
|
||||
let deferred = promise.defer();
|
||||
let request = {
|
||||
|
@ -3,7 +3,9 @@
|
||||
|
||||
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
const {require} = devtools;
|
||||
const {installHosted, installPackaged} = require("devtools/app-actor-front");
|
||||
const AppActorFront = require("devtools/app-actor-front");
|
||||
const {installHosted, installPackaged} = AppActorFront;
|
||||
const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
|
||||
let gAppId = "actor-test";
|
||||
const APP_ORIGIN = "app://" + gAppId;
|
||||
@ -178,29 +180,54 @@ add_test(function testFileUploadInstall() {
|
||||
// Disable the bulk trait temporarily to test the JSON upload path
|
||||
gClient.traits.bulk = false;
|
||||
|
||||
installPackaged(gClient, gActor, packageFile.path, gAppId)
|
||||
let progressDeferred = promise.defer();
|
||||
// Ensure we get at least one progress event at the end
|
||||
AppActorFront.on("install-progress", function onProgress(e, progress) {
|
||||
if (progress.bytesSent == progress.totalBytes) {
|
||||
AppActorFront.off("install-progress", onProgress);
|
||||
progressDeferred.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
let installed =
|
||||
installPackaged(gClient, gActor, packageFile.path, gAppId)
|
||||
.then(function ({ appId }) {
|
||||
do_check_eq(appId, gAppId);
|
||||
|
||||
// Restore default bulk trait value
|
||||
gClient.traits.bulk = true;
|
||||
|
||||
run_next_test();
|
||||
}, function (e) {
|
||||
do_throw("Failed install uploaded packaged app: " + e.error + ": " + e.message);
|
||||
});
|
||||
|
||||
promise.all([progressDeferred.promise, installed])
|
||||
.then(() => {
|
||||
// Restore default bulk trait value
|
||||
gClient.traits.bulk = true;
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function testBulkUploadInstall() {
|
||||
let packageFile = do_get_file("data/app.zip");
|
||||
do_check_true(gClient.traits.bulk);
|
||||
installPackaged(gClient, gActor, packageFile.path, gAppId)
|
||||
|
||||
let progressDeferred = promise.defer();
|
||||
// Ensure we get at least one progress event at the end
|
||||
AppActorFront.on("install-progress", function onProgress(e, progress) {
|
||||
if (progress.bytesSent == progress.totalBytes) {
|
||||
AppActorFront.off("install-progress", onProgress);
|
||||
progressDeferred.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
let installed =
|
||||
installPackaged(gClient, gActor, packageFile.path, gAppId)
|
||||
.then(function ({ appId }) {
|
||||
do_check_eq(appId, gAppId);
|
||||
run_next_test();
|
||||
}, function (e) {
|
||||
do_throw("Failed bulk install uploaded packaged app: " + e.error + ": " + e.message);
|
||||
});
|
||||
|
||||
promise.all([progressDeferred.promise, installed])
|
||||
.then(run_next_test);
|
||||
});
|
||||
|
||||
add_test(function testInstallHosted() {
|
||||
@ -250,4 +277,3 @@ function run_test() {
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
|
@ -663,6 +663,8 @@ DebuggerClient.prototype = {
|
||||
* @return Promise
|
||||
* The promise is resolved when copying completes or
|
||||
* rejected if any (unexpected) errors occur.
|
||||
* This object also emits "progress" events for each chunk
|
||||
* that is copied. See stream-utils.js.
|
||||
*/
|
||||
request: function (aRequest, aOnResponse) {
|
||||
if (!this.mainRoot) {
|
||||
@ -728,6 +730,8 @@ DebuggerClient.prototype = {
|
||||
* @return Promise
|
||||
* The promise is resolved when copying completes or
|
||||
* rejected if any (unexpected) errors occur.
|
||||
* This object also emits "progress" events for each chunk
|
||||
* that is copied. See stream-utils.js.
|
||||
* * json-reply: The server replied with a JSON packet, which is
|
||||
* passed as event data.
|
||||
* * bulk-reply: The server replied with bulk data, which you can read
|
||||
@ -753,6 +757,8 @@ DebuggerClient.prototype = {
|
||||
* @return Promise
|
||||
* The promise is resolved when copying completes or
|
||||
* rejected if any (unexpected) errors occur.
|
||||
* This object also emits "progress" events for each chunk
|
||||
* that is copied. See stream-utils.js.
|
||||
*/
|
||||
startBulkRequest: function(request) {
|
||||
if (!this.traits.bulk) {
|
||||
@ -932,6 +938,8 @@ DebuggerClient.prototype = {
|
||||
* @return Promise
|
||||
* The promise is resolved when copying completes or rejected
|
||||
* if any (unexpected) errors occur.
|
||||
* This object also emits "progress" events for each chunk
|
||||
* that is copied. See stream-utils.js.
|
||||
*/
|
||||
onBulkPacket: function(packet) {
|
||||
let { actor, type, length } = packet;
|
||||
|
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]
|
@ -71,6 +71,25 @@ exports.items = [
|
||||
name: "csscoverage toggle",
|
||||
hidden: true,
|
||||
description: l10n.lookup("csscoverageToggleDesc2"),
|
||||
state: {
|
||||
isChecked: function(target) {
|
||||
return csscoverage.getUsage(target).then(usage => {
|
||||
return usage.isRunning();
|
||||
});
|
||||
},
|
||||
onChange: function(target, handler) {
|
||||
csscoverage.getUsage(target).then(usage => {
|
||||
this.handler = ev => { handler("state-change", ev); };
|
||||
usage.on("state-change", this.handler);
|
||||
});
|
||||
},
|
||||
offChange: function(target, handler) {
|
||||
csscoverage.getUsage(target).then(usage => {
|
||||
usage.off("state-change", this.handler);
|
||||
this.handler = undefined;
|
||||
});
|
||||
},
|
||||
},
|
||||
exec: function*(args, context) {
|
||||
let target = context.environment.target;
|
||||
let usage = yield csscoverage.getUsage(target);
|
||||
@ -78,13 +97,8 @@ exports.items = [
|
||||
throw new Error(l10n.lookup("csscoverageNoRemoteError"));
|
||||
}
|
||||
|
||||
let running = yield usage.toggle();
|
||||
if (running) {
|
||||
return l10n.lookup("csscoverageRunningReply");
|
||||
}
|
||||
|
||||
yield usage.stop();
|
||||
yield gDevTools.showToolbox(target, "styleeditor");
|
||||
yield usage.toggle(context.environment.chromeWindow,
|
||||
context.environment.target);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -17,6 +17,7 @@ PARALLEL_DIRS += [
|
||||
'qrcode',
|
||||
'transport',
|
||||
'tern',
|
||||
'discovery'
|
||||
]
|
||||
|
||||
MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
|
||||
|
@ -7,7 +7,9 @@
|
||||
const { Cc, Ci, Cu } = require("chrome");
|
||||
|
||||
const Services = require("Services");
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const events = require("sdk/event/core");
|
||||
const protocol = require("devtools/server/protocol");
|
||||
const { method, custom, RetVal, Arg } = protocol;
|
||||
|
||||
@ -75,6 +77,13 @@ const l10n = exports.l10n = {
|
||||
let UsageReportActor = protocol.ActorClass({
|
||||
typeName: "usageReport",
|
||||
|
||||
events: {
|
||||
"state-change" : {
|
||||
type: "stateChange",
|
||||
stateChange: Arg(0, "json")
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function(conn, tabActor) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
|
||||
@ -83,6 +92,9 @@ let UsageReportActor = protocol.ActorClass({
|
||||
|
||||
this._onTabLoad = this._onTabLoad.bind(this);
|
||||
this._onChange = this._onChange.bind(this);
|
||||
|
||||
this._notifyOn = Ci.nsIWebProgress.NOTIFY_STATUS |
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_ALL
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
@ -107,12 +119,34 @@ let UsageReportActor = protocol.ActorClass({
|
||||
this._running = true;
|
||||
this._tooManyUnused = false;
|
||||
|
||||
this._tabActor.browser.addEventListener("load", this._onTabLoad, true);
|
||||
this._progressListener = {
|
||||
QueryInterface: XPCOMUtils.generateQI([ Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference ]),
|
||||
|
||||
this._observeMutations(this._tabActor.window.document);
|
||||
onStateChange: (progress, request, flags, status) => {
|
||||
let isStop = flags & Ci.nsIWebProgressListener.STATE_STOP;
|
||||
let isWindow = flags & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
|
||||
|
||||
if (isStop && isWindow) {
|
||||
this._onTabLoad(progress.DOMWindow.document);
|
||||
}
|
||||
},
|
||||
|
||||
onLocationChange: () => {},
|
||||
onProgressChange: () => {},
|
||||
onSecurityChange: () => {},
|
||||
onStatusChange: () => {},
|
||||
destroy: () => {}
|
||||
};
|
||||
|
||||
this._progress = this._tabActor.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebProgress);
|
||||
this._progress.addProgressListener(this._progressListener, this._notifyOn);
|
||||
|
||||
this._populateKnownRules(this._tabActor.window.document);
|
||||
this._updateUsage(this._tabActor.window.document, false);
|
||||
|
||||
events.emit(this, "state-change", { isRunning: true });
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -123,19 +157,18 @@ let UsageReportActor = protocol.ActorClass({
|
||||
throw new Error(l10n.lookup("csscoverageNotRunningError"));
|
||||
}
|
||||
|
||||
this._tabActor.browser.removeEventListener("load", this._onTabLoad, true);
|
||||
this._progress.removeProgressListener(this._progressListener, this._notifyOn);
|
||||
this._progress = undefined;
|
||||
|
||||
this._running = false;
|
||||
events.emit(this, "state-change", { isRunning: false });
|
||||
}),
|
||||
|
||||
/**
|
||||
* Start/stop recording usage data depending on what we're currently doing.
|
||||
*/
|
||||
toggle: method(function() {
|
||||
return this._running ?
|
||||
this.stop().then(() => false) :
|
||||
this.start().then(() => true);
|
||||
}, {
|
||||
response: RetVal("boolean"),
|
||||
return this._running ? this.stop() : this.start();
|
||||
}),
|
||||
|
||||
/**
|
||||
@ -155,10 +188,9 @@ let UsageReportActor = protocol.ActorClass({
|
||||
}),
|
||||
|
||||
/**
|
||||
* Called from the tab "load" event
|
||||
* Called by the ProgressListener to simulate a "load" event
|
||||
*/
|
||||
_onTabLoad: function(ev) {
|
||||
let document = ev.target;
|
||||
_onTabLoad: function(document) {
|
||||
this._populateKnownRules(document);
|
||||
this._updateUsage(document, true);
|
||||
|
||||
@ -435,15 +467,6 @@ let UsageReportActor = protocol.ActorClass({
|
||||
response: RetVal("json")
|
||||
}),
|
||||
|
||||
/**
|
||||
* For testing only. Is css coverage running.
|
||||
*/
|
||||
_testOnly_isRunning: method(function() {
|
||||
return this._running;
|
||||
}, {
|
||||
response: { value: RetVal("boolean") }
|
||||
}),
|
||||
|
||||
/**
|
||||
* For testing only. What pages did we visit.
|
||||
*/
|
||||
@ -697,6 +720,16 @@ const sheetToUrl = exports.sheetToUrl = function(stylesheet) {
|
||||
throw new Error("Unknown sheet source");
|
||||
}
|
||||
|
||||
/**
|
||||
* Running more than one usage report at a time is probably bad for performance
|
||||
* and it isn't particularly useful, and it's confusing from a notification POV
|
||||
* so we only allow one.
|
||||
*/
|
||||
let isRunning = false;
|
||||
let notification;
|
||||
let target;
|
||||
let chromeWindow;
|
||||
|
||||
/**
|
||||
* Front for UsageReportActor
|
||||
*/
|
||||
@ -707,31 +740,46 @@ const UsageReportFront = protocol.FrontClass(UsageReportActor, {
|
||||
this.manage(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Server-side start is above. Client-side start adds a notification box
|
||||
*/
|
||||
start: custom(function(chromeWindow, target) {
|
||||
if (chromeWindow != null) {
|
||||
let gnb = chromeWindow.document.getElementById("global-notificationbox");
|
||||
this.notification = gnb.getNotificationWithValue("csscoverage-running");
|
||||
_onStateChange: protocol.preEvent("state-change", function(ev) {
|
||||
isRunning = ev.isRunning;
|
||||
ev.target = target;
|
||||
|
||||
if (this.notification == null) {
|
||||
let notifyStop = ev => {
|
||||
if (ev == "removed") {
|
||||
if (isRunning) {
|
||||
let gnb = chromeWindow.document.getElementById("global-notificationbox");
|
||||
notification = gnb.getNotificationWithValue("csscoverage-running");
|
||||
|
||||
if (notification == null) {
|
||||
let notifyStop = reason => {
|
||||
if (reason == "removed") {
|
||||
this.stop();
|
||||
gDevTools.showToolbox(target, "styleeditor");
|
||||
}
|
||||
};
|
||||
|
||||
let msg = l10n.lookup("csscoverageRunningReply");
|
||||
this.notification = gnb.appendNotification(msg,
|
||||
"csscoverage-running",
|
||||
"", // i.e. no image
|
||||
gnb.PRIORITY_INFO_HIGH,
|
||||
null, // i.e. no buttons
|
||||
notifyStop);
|
||||
notification = gnb.appendNotification(msg, "csscoverage-running",
|
||||
"", // i.e. no image
|
||||
gnb.PRIORITY_INFO_HIGH,
|
||||
null, // i.e. no buttons
|
||||
notifyStop);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (notification) {
|
||||
notification.remove();
|
||||
notification = undefined;
|
||||
}
|
||||
|
||||
gDevTools.showToolbox(target, "styleeditor");
|
||||
target = undefined;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Server-side start is above. Client-side start adds a notification box
|
||||
*/
|
||||
start: custom(function(newChromeWindow, newTarget) {
|
||||
target = newTarget;
|
||||
chromeWindow = newChromeWindow;
|
||||
|
||||
return this._start();
|
||||
}, {
|
||||
@ -739,18 +787,23 @@ const UsageReportFront = protocol.FrontClass(UsageReportActor, {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Client-side stop also removes the notification box
|
||||
* Server-side start is above. Client-side start adds a notification box
|
||||
*/
|
||||
stop: custom(function() {
|
||||
if (this.notification != null) {
|
||||
this.notification.remove();
|
||||
this.notification = undefined;
|
||||
}
|
||||
toggle: custom(function(newChromeWindow, newTarget) {
|
||||
target = newTarget;
|
||||
chromeWindow = newChromeWindow;
|
||||
|
||||
return this._stop();
|
||||
return this._toggle();
|
||||
}, {
|
||||
impl: "_stop"
|
||||
impl: "_toggle"
|
||||
}),
|
||||
|
||||
/**
|
||||
* We count STARTING and STOPPING as 'running'
|
||||
*/
|
||||
isRunning: function() {
|
||||
return isRunning;
|
||||
}
|
||||
});
|
||||
|
||||
exports.UsageReportFront = UsageReportFront;
|
||||
|
@ -39,7 +39,8 @@ let addonManager = null;
|
||||
* about them.
|
||||
*/
|
||||
function mapURIToAddonID(uri, id) {
|
||||
if ((Services.appinfo.ID || undefined) == B2G_ID) {
|
||||
if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT ||
|
||||
(Services.appinfo.ID || undefined) == B2G_ID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -5058,7 +5059,13 @@ ThreadSources.prototype = {
|
||||
.QueryInterface(Ci.nsIURL);
|
||||
if (url.fileExtension === "js") {
|
||||
spec.contentType = "text/javascript";
|
||||
spec.text = aScript.source.text;
|
||||
// If the Debugger API wasn't able to load the source,
|
||||
// because sources were discarded
|
||||
// (javascript.options.discardSystemSource == true),
|
||||
// give source() a chance to fetch them.
|
||||
if (aScript.source.text != "[no source]") {
|
||||
spec.text = aScript.source.text;
|
||||
}
|
||||
}
|
||||
} catch(ex) {
|
||||
// Not a valid URI.
|
||||
|
@ -1199,6 +1199,8 @@ DebuggerServerConnection.prototype = {
|
||||
* @return Promise
|
||||
* The promise is resolved when copying completes or rejected
|
||||
* if any (unexpected) errors occur.
|
||||
* This object also emits "progress" events for each chunk
|
||||
* that is copied. See stream-utils.js.
|
||||
*/
|
||||
onBulkPacket: function(packet) {
|
||||
let { actor: actorKey, type, length } = packet;
|
||||
|
@ -28,6 +28,7 @@ const { Cc, Ci, Cu } = require("chrome");
|
||||
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
const { dumpn, dumpv } = DevToolsUtils;
|
||||
const StreamUtils = require("devtools/toolkit/transport/stream-utils");
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
DevToolsUtils.defineLazyGetter(this, "unicodeConverter", () => {
|
||||
const unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
@ -274,8 +275,9 @@ BulkPacket.prototype.read = function(stream) {
|
||||
length: this.length,
|
||||
copyTo: (output) => {
|
||||
dumpv("CT length: " + this.length);
|
||||
deferred.resolve(StreamUtils.copyStream(stream, output, this.length));
|
||||
return deferred.promise;
|
||||
let copying = StreamUtils.copyStream(stream, output, this.length);
|
||||
deferred.resolve(copying);
|
||||
return copying;
|
||||
},
|
||||
stream: stream,
|
||||
done: deferred
|
||||
@ -323,8 +325,9 @@ BulkPacket.prototype.write = function(stream) {
|
||||
this._readyForWriting.resolve({
|
||||
copyFrom: (input) => {
|
||||
dumpv("CF length: " + this.length);
|
||||
deferred.resolve(StreamUtils.copyStream(input, stream, this.length));
|
||||
return deferred.promise;
|
||||
let copying = StreamUtils.copyStream(input, stream, this.length);
|
||||
deferred.resolve(copying);
|
||||
return copying;
|
||||
},
|
||||
stream: stream,
|
||||
done: deferred
|
||||
|
@ -8,6 +8,7 @@ const { Ci, Cc, Cu, Cr, CC } = require("chrome");
|
||||
const Services = require("Services");
|
||||
const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
|
||||
const { dumpv } = DevToolsUtils;
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
|
||||
DevToolsUtils.defineLazyGetter(this, "IOUtil", () => {
|
||||
return Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);
|
||||
@ -59,6 +60,7 @@ function copyStream(input, output, length) {
|
||||
}
|
||||
|
||||
function StreamCopier(input, output, length) {
|
||||
EventEmitter.decorate(this);
|
||||
this._id = StreamCopier._nextId++;
|
||||
this.input = input;
|
||||
// Save off the base output stream, since we know it's async as we've required
|
||||
@ -70,13 +72,20 @@ function StreamCopier(input, output, length) {
|
||||
createInstance(Ci.nsIBufferedOutputStream);
|
||||
this.output.init(output, BUFFER_SIZE);
|
||||
}
|
||||
this._length = length;
|
||||
this._amountLeft = length;
|
||||
this._deferred = promise.defer();
|
||||
|
||||
this._copy = this._copy.bind(this);
|
||||
this._flush = this._flush.bind(this);
|
||||
this._destroy = this._destroy.bind(this);
|
||||
this._deferred.promise.then(this._destroy, this._destroy);
|
||||
|
||||
// Copy promise's then method up to this object.
|
||||
// Allows the copier to offer a promise interface for the simple succeed or
|
||||
// fail scenarios, but also emit events (due to the EventEmitter) for other
|
||||
// states, like progress.
|
||||
this.then = this._deferred.promise.then.bind(this._deferred.promise);
|
||||
this.then(this._destroy, this._destroy);
|
||||
|
||||
// Stream ready callback starts as |_copy|, but may switch to |_flush| at end
|
||||
// if flushing would block the output stream.
|
||||
@ -86,15 +95,17 @@ StreamCopier._nextId = 0;
|
||||
|
||||
StreamCopier.prototype = {
|
||||
|
||||
get copied() { return this._deferred.promise; },
|
||||
|
||||
copy: function() {
|
||||
try {
|
||||
this._copy();
|
||||
} catch(e) {
|
||||
this._deferred.reject(e);
|
||||
}
|
||||
return this.copied;
|
||||
// Dispatch to the next tick so that it's possible to attach a progress
|
||||
// event listener, even for extremely fast copies (like when testing).
|
||||
Services.tm.currentThread.dispatch(() => {
|
||||
try {
|
||||
this._copy();
|
||||
} catch(e) {
|
||||
this._deferred.reject(e);
|
||||
}
|
||||
}, 0);
|
||||
return this;
|
||||
},
|
||||
|
||||
_copy: function() {
|
||||
@ -115,6 +126,7 @@ StreamCopier.prototype = {
|
||||
this._amountLeft -= bytesCopied;
|
||||
this._debug("Copied: " + bytesCopied +
|
||||
", Left: " + this._amountLeft);
|
||||
this._emitProgress();
|
||||
|
||||
if (this._amountLeft === 0) {
|
||||
this._debug("Copy done!");
|
||||
@ -126,6 +138,13 @@ StreamCopier.prototype = {
|
||||
this.input.asyncWait(this, 0, 0, Services.tm.currentThread);
|
||||
},
|
||||
|
||||
_emitProgress: function() {
|
||||
this.emit("progress", {
|
||||
bytesSent: this._length - this._amountLeft,
|
||||
totalBytes: this._length
|
||||
});
|
||||
},
|
||||
|
||||
_flush: function() {
|
||||
try {
|
||||
this.output.flush();
|
||||
|
@ -88,6 +88,8 @@ const PACKET_HEADER_MAX = 200;
|
||||
* @return Promise
|
||||
* The promise is resolved when copying completes or rejected if any
|
||||
* (unexpected) errors occur.
|
||||
* This object also emits "progress" events for each chunk that is
|
||||
* copied. See stream-utils.js.
|
||||
*
|
||||
* - onClosed(reason) - called when the connection is closed. |reason| is
|
||||
* an optional nsresult or object, typically passed when the transport is
|
||||
@ -172,6 +174,8 @@ DebuggerTransport.prototype = {
|
||||
* @return Promise
|
||||
* The promise is resolved when copying completes or
|
||||
* rejected if any (unexpected) errors occur.
|
||||
* This object also emits "progress" events for each chunk
|
||||
* that is copied. See stream-utils.js.
|
||||
*/
|
||||
startBulkSend: function(header) {
|
||||
let packet = new BulkPacket(this);
|
||||
@ -576,9 +580,10 @@ LocalDebuggerTransport.prototype = {
|
||||
type: type,
|
||||
length: length,
|
||||
copyTo: (output) => {
|
||||
deferred.resolve(
|
||||
StreamUtils.copyStream(pipe.inputStream, output, length));
|
||||
return deferred.promise;
|
||||
let copying =
|
||||
StreamUtils.copyStream(pipe.inputStream, output, length);
|
||||
deferred.resolve(copying);
|
||||
return copying;
|
||||
},
|
||||
stream: pipe.inputStream,
|
||||
done: deferred
|
||||
@ -598,9 +603,10 @@ LocalDebuggerTransport.prototype = {
|
||||
|
||||
sendDeferred.resolve({
|
||||
copyFrom: (input) => {
|
||||
copyDeferred.resolve(
|
||||
StreamUtils.copyStream(input, pipe.outputStream, length));
|
||||
return copyDeferred.promise;
|
||||
let copying =
|
||||
StreamUtils.copyStream(input, pipe.outputStream, length);
|
||||
copyDeferred.resolve(copying);
|
||||
return copying;
|
||||
},
|
||||
stream: pipe.outputStream,
|
||||
done: copyDeferred
|
||||
|
Loading…
Reference in New Issue
Block a user