mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-27 01:20:56 +00:00
4be8027b21
- adapted version of Bug 912121 - Migrate major DevTools directories (8ff521cb21)
- Bug 1187831 - Refactor the highlighters into separate modules; r=pbrosset (fdca9d5f16)
- Bug 1157906 - Can't return arrays as a root response, fixes inspect button. r=bgrins (f684642158)
- Bug 912121 - Adjust build configs and test manifests. r=glandium (304066d667)
- Bug 912121 - Package DevTools client content in devtools.jar. rs=devtools (967caca3e0)
- Bug 1074180 - Convert touch simulator to frame script for e10s. r=ochameau (be9559c94c)
- Bug 1178851 - Move testing flag to DevToolsUtils. r=ochameau (9eb0bb908f)
- Bug 1158634 - Modify font-inspector tests for better code sharing. r=pbrosset (6e5f42e1fc)
- Bug 859058 - Export content of the Network panel as HAR; r=jsantell, r=jlongster (629f4c8f63)
- fix some module paths and Telemetry init (ed5131d7e1)
- Bug 1167945 - Remove spidermonkey specific JS from canvasdebugger; r=jryans (4865569865)
- Bug 1188401 - Expose require out of Loader.jsm and use it. r=jryans (464953c07a)
- Bug 1175760 - Set recording configurations based on UI prefs, but also target support overrides UI prefs. r=vp (137e7940db)
- Bug 1172180 - Create a PerformanceActor, and migrate existing pseudo PerformanceFront to a legacy front. r=vp (0fa20d2d20)
- Bug 1172180 - Add polling events for profiler/buffer status on the profiler itself. r=vp (ef4bb72f73)
- Bug 1172180 - part 2 - add ability to auto drain allocation logs in memory component via a timer and on GC. r=fitzgen (46ae8dd7eb)
- Bug 1190857 - Add loader alias to DevTools loader exports. r=jryans (fbe9e189b0)
- Bug 1195825 - Replace Cu.import(Promise.jsm) by require(promise) in devtools. r=jryans (be39379f9a)
- Bug 1153305 - change css-tokenizer.js to use CSSLexer r=pbrosset (584a825504)
- Bug 1178535 - Part 1: Parse the pseudoclasses and attributes from the selector text r=tromney (9d761b2ecf)
- Bug 1173298 - Disable add rule button for non-element nodes and anonymous elements. r=bgrins (1c60a6580c)
- move themes from toolkit to devtools (97989c5768)
- Bug 1190452 - Always import toolkit/devtools/server/main.js as CommonJS module. r=jryans (4b651f7076)
- Bug 1192863 - Use client/main.js instead of dbg-server.jsm. r=jryans (becdb5489e)
- minor cleanup (ba24674685)
- Bug 999568 - Bring devtools window to front on F12. r=pbrosset (8e945fb777)
- move devtool locales (52ddf02403)
- Bug 912121 - Adjust ESLint files. r=pbrosset (59724e31e3)
- Bug 1204182 - Use a single jar manifest for shared theme resources. r=dao (8b396bafe1)
- fix double entry (e64c5bb511)
- Bug 1077339 - Display keyboard shortcuts when hovering panel tabs. r=janx f=bgrins (1422fe5127)
- Bug 912121 - Rewrite require / import to match source tree. rs=devtools (09c4767e9f)
- Bug 1193313 - Remove projecteditor promise alias in favor of require(promise). r=bgrins (8ed93e1a8f)
- Bug 1193313 - Cleanup promises. r=jryans (38508b18ea)
- Bug 1193313 - Make gcli use DOM Promise. r=jwalker (405e49ec4e)
- Bug 1160361 - Skip Telemetry calls for non-Firefox. r=bgrins (942761962e)
- Bug 1189386 - Paint Flashing button on Developers Toolbar is checked on new tabs. r=pbrosset (d2cd46a7f2)
- Bug 1197394 - GCLI's number-type doesn't ignore min or max-values of 0. r=jwalker (5bda36049b)
- Bug 1046049 - Fix :w for saving in webide. r=jryans (8b353db8ca)
- Bug 1148086 - Style Editor: Don't jump to the first line unconditionally when an editor is shown. r=bgrins (25cd83f12f)
- Bug 1049199 - Style editor should show XBL stylesheets. r=pbrosset Bug 1148770 - Wait for asynchronous operations to complete before resolving in StyleSheetEditor.fetchSource. r=ejpbruel (0d3f4ff86f)
- Bug 1148770 - Don't load CodeMirror editor if StyleSheetEditor is destroyed before load is called. r=bgrins (5a665dca6e)
- Bug 999299 - Remove '[]' from the autoCloseBrackets config for Style Editor sourceeditor;r=mratcliffe (58aaf86e1d)
- Bug 1177891 - Introduce redux-style UI architecture in the debugger and refactor event listeners to use it. r=fitzgen Bug 1204173 - Replace Fluxify with Redux. r=jlongster (145adb12e4)
- Bug 1176981 - Part 0: Remove the tracing debugger UI; r=vporof (92d7fb4f3d)
- Bug 1176981 - Part 1: Remove the tracing debugger actor from the server; r=vporof (6548c82f8e)
- Bug 1176981 - Part 3: Actually remove the tracing debugger pref; r=vporof (731656b547)
- Bug 912121 - Create shims for popular DevTools modules in add-ons. rs=devtools (4635ccf492)
- partial of Bug 912121 - Update misc. DevTools paths and comments. rs=devtools (3b00d87f89)
- Bug 1189128 - Rename devtools .inc.css files that are no longer included to .css;r=jryans (b0fffb3be1)
- Bug 912121 - Misc. DevTools test fixes after migration. rs=devtools (6285f7ccd3)
- Bug 912121 - Package DevTools client themes in devtools.jar. rs=devtools (697be51a28)
- Bug 912121 - Create shims for popular DevTools themes in add-ons. rs=devtools (903c0e721e)
- Bug 912121 - Migrate DevTools themes. rs=devtools (a64b30b44f)
- Bug 1201597 - Part 0: Make saveHeapSnapshot return the file path rather than take it as a parameter; r=bholley (85652707da)
- Bug 1200446 - Add a method for saving heap snapshots to MemoryActor; r=jryans (112af304d1)
- Bug 1201213 - Add a HeapAnalyses{Worker,Client} for running heap analyses; r=jsantell (3e43273721)
- Bug 1201597 - Part 1: Implement an nsIOutputStream for ipc::FileDescriptor; r=mrbkap (e659f8514d)
- Bug 1201597 - Part 2: Add an IPDL subprotocol for opening core dump files to (f9c9b2b747)
- missing bit of Bug 1188401 - Expose require out of Loader.jsm (c7174fce60)
- Bug 1191916 - only enable step in/over/out buttons if actually paused at a location r=bgrins Bug 789430 - Pause on next bytecode instead of immediately;r=fitzgen (d51da724b3)
- Bug 1201700 - Create skeleton memory tool. r=fitzgen (4217e71fd4)
- missing bit of Bug 1178851 (1804598f62)
- add missing bit (c72fbab8a6)
- fix (2ea8917252)
- Bug 1182194 - Remove DevToolsUtils.jsm (in favor of its module). r=jwalker (16cd60ce9a)
- Bug 1182194 - Convert LayoutHelpers.jsm into a commonjs module. r=jwalker Bug 1132475 - Refactor LayoutHelpers.jsm to avoid dependencies. r=pbrosset (5409fcdaac)
- Bug 935366 - Remove unused Require.jsm. r=fitzgen (20ce6ce7f4)
- remove unbuild profiler and timeline, but build shims (c0d9a9048a)
- Bug 1169135 - Make Graphs.jsm a require module, r=jsantell (8bd010851b)
- Bug 1137487 - Make tests wait for the canvas graph's destruction to finish before cleaning up, r=me (ba472b7c63)
- Bug 1151246 - flame graph header should render above the flame graph. r=vp (6f0ca96884)
- Bug 1066504 - Allow timeline range selection to continue even after mouseout of the graph window. r=vporof Bug 1145784 - Bind graph mouse movement to the top level window;r=vporof (7556cd2cf7)
- Bug 1141719 - Lots of "TypeError: ownerWindow is null" when shutting down the new perf tool on debug builds, r=jsantell (12761630dc)
- Bug 1175686 - Pull line and bar graph widgets outside of Graphs.js into their own files, r=jsantell, a=Mossop (e6e895604a)
- Bug 1174264 - Stacked Mountain Chart Widget, r=jsantell, a=Mossop (c1ed5dfed1)
- Bug 980006 - Add MDN tooltips for CSS properties. r=pbrosset (dc7102fa19)
- Bug 1159109 - The MDN tooltip should be controlled by a pref. r=pbrosset (95da8e51a5)
- missing bits of Bug 1182194 - Use DevToolsUtils module instead of JSM. (355c4e2f80)
- Bug 1141615 - Use before declaration for scaledStep in FlameGraph.jsm, r=jsantell (993c41aae5)
- Bug 1145784 - Use testX/testY for coordinates during tests for the Graph and FlameGraph;r=vporof (a004e9c7f0)
- Bug 1090950 - Properly bound fillRect call for Graphs with selections that start or end out of bounds. r=vporof (3fbf750cbd)
- Bug 1164327 - Make resizing a graph maintain the drawn selection. r=vporof (147485835b)
- Don't dump errors in tests that are already handled by the parser (bug 1138784). r=vporof (e0671fd8b3)
- Bug 913617 - Reflect.parse: rename ArrowExpression to ArrowFunctionExpression. r=jorendorff (78eb0f718b)
- Bug 1008380 - Can't remove a breakpoint after it slides to a new line;r=jlong (4fd797cf9a)
- Bug 1131756 - properly display anonymous sources when they are needed in the debugger r=ejpbruel Bug 1183895 - Part 2: Add a toolbar button for toggling the promise debugger r=fitzgen (5a01328a1e)
- Bug 1183895 - Part 1: Add a new pane for the promise debugger r=fitzgen (20a1f49b59)
- Bug 1179977 - Vertical layout for JSDebugger in small window host. r=jlong (4bb82acdb6)
- some profiler/performance leftover (921b05d184)
- Bug 1155553 - Fixes animationinspector regression with actor-capabilities detection; r=bgrins (12095c1afc)
- Bug 1169563 - 1 - Minor ESLint code cleanup; r=ednapiranha (c6c808ca1b)
- Bug 1169563 - 2 - Add a scrubber element to timeline animation UI; r=zer0 (44caa900cd)
- Bug 1169563 - 3 - Move the scrubber in the animation timeline to set the time; r=tromey (71ee83e303)
- Bug 1169563 - 5 - Resize animations in the timeline UI to show their rates; r=miker (63709f41b6)
- Bug 1169563 - 6 - Animate the scrubber when animations are playing; r=miker (3f63035572)
- Bug 1174060 - 4 - Minor eslint changes to animation-inspector; r=ednapiranha (42c3242e87)
- missing eslint stuff (c4ad512ec9)
- Bug 1090478 - Add a keyboard shortcut for the Browser Toolbox. r=jryans (f5ec8fce1d)
- Bug 1135635 - Position sidebar allTabs menu absolutely to avoid oveflow jittering; r=bgrins (7339c7344a)
- Bug 1182083 - Don't hide tabpanels in the DevTools sidebar. r=vporof (9ed0784990)
- some telemetry stuff (3844f60f89)
- Bug 1172010 - Fix tools reload gcli command. r=jwalker (c3c2ab47ae)
- Bug 1107656 - Generate unique prefixes for e10s parent-child server connections; r=ochameau (da59ead659)
- fix image paths (085de78f74)
- Bug 116953 - 4 - Move traits out of controller; r=tromey (80e5d0ba8d)
- Bug 1174060 - 1 - Fix auto-removed animations that have the same name but different targets; r=miker (0ac111d790)
- Bug 1174060 - 2 - Display negative delays like normal delays; r=bgrins (530b611f97)
- Bug 1174060 - 5 - Reverts typo introduced by bug 1180134 that overrided the background-image; r=tromey (104def3b4d)
- Bug 1180134 - Color code animations and transitions so they look different; r=bgrins (fdfa5b131c)
- Bug 1174060 - 6 - Tests for how delays are displayed in the timeline; r=ochameau (26422f6438)
- Bu 1155661 - 1 - Rename toolbar to global-toolbar; r=zer0 (ccf6e8c304)
- Bug 1155661 - 2 - Add a new timeline toolbar shown only when the timeline is displayed and non empty; r=zer0 (1a4de3a552)
- Bug 1202443 - Prevent pushing the timeline header up when there are many animations; r=zer0 (7eb9645dd8)
- fix some paths (fd03ff798b)
- Bug 1155661 - 3 - Add a way to play/pause the current animations at the same time; r=miker (582c82e3a9)
- Bug 1156757 - Turn ON the animation inspector UI v3 by default and remove v2; r=past (0093d6e0cc)
- Bug 1155663 - More tests for the timeline-based animation inspector UI; r=bgrins (5138da1bf1)
- Bug 1155661 - 4 - Add the timeline play/pause button; r=bgrins (2af3664fdd)
- Bug 1155661 - 5 - Make the timeline emit data about its current state and update the play/pause button based on this; r=miker (2b56df070d)
- Bug 1155661 - 6 - Implement the behavior behind the timeline play/pause button; r=miker (783e49d1ae)
- Bug 1173761 - Display each animation's time data like duration in a tooltip; r=miker (312b319c21)
- Bug 1155651 - Return all players below a node in getAnimationPlayersForNode; r=miker (38c6e3b365)
- Bug 1170806 - Fix GeneratedLocation documentation comment DONTBUILD; r=me (3f1f605e15)
- Bug 1150814 - Rulers graduations are not displayed. r=mratcliffe (48b79dae20)
- Bug 938188 - Make highlighter capable of highlighting only one region, fading out others. r=bgrins (ceb4211e87)
- Bug 1151956 - Add tooltips with refs to source css rules in the layout-view; r=miker (b72cab14da)
- Bug 1167617 - Remove SpiderMonkey specific JS syntax from inspector; r=jryans (9c95101e22)
- Bug 1149346 - First word in selector-search also matches classes and ids; r=harth (82159de025)
- Bug 1196776 - Fix dynamic actors in browser content toolbox. r=jryans (3370eef446)
- Bug 1174821 - 1 - eslint cleanup of view.js; r=janx (fa3ecba9a2)
- Bug 1174821 - 2 - Show only 1 box-model region on hover; r=janx (ad1ddab34a)
- Bug 1174821 - 3 - Consolidate and cleanup layoutview's css files; r=janx (8baa3fd80b)
- Bug 1174821 - 4 - Refactor layoutview's dimming; r=janx (37d5849be5)
- Bug 1174821 - 5 - More eslint cleanup of view.js; r=janx (82200173a0)
- Bug 1173373 - Change DevTools tab tooltips from Cmd+Alt* to Cmd+Opt+*;r=janx (23f9900033)
- Bug 1138591 - Remove the chrome-enabled pref. r=past (8bfd0a8d14)
- Bug 1195162 - Remove unused pref layout.debug.enable_data_xbl (r=sicking) (04c4996c90)
- fix misspatch (975349f11b)
- fix misspatch (c9ef2c63de)
754 lines
23 KiB
JavaScript
754 lines
23 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
/* 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";
|
|
|
|
var { Ci, Cc, CC, Cr, Cu } = require("chrome");
|
|
|
|
// Ensure PSM is initialized to support TLS sockets
|
|
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
|
|
|
|
var Services = require("Services");
|
|
var promise = require("promise");
|
|
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
|
var { dumpn, dumpv } = DevToolsUtils;
|
|
loader.lazyRequireGetter(this, "DebuggerTransport",
|
|
"devtools/shared/transport/transport", true);
|
|
loader.lazyRequireGetter(this, "DebuggerServer",
|
|
"devtools/server/main", true);
|
|
loader.lazyRequireGetter(this, "discovery",
|
|
"devtools/shared/discovery/discovery");
|
|
loader.lazyRequireGetter(this, "cert",
|
|
"devtools/shared/security/cert");
|
|
loader.lazyRequireGetter(this, "Authenticators",
|
|
"devtools/shared/security/auth", true);
|
|
loader.lazyRequireGetter(this, "AuthenticationResult",
|
|
"devtools/shared/security/auth", true);
|
|
loader.lazyRequireGetter(this, "setTimeout", "Timer", true);
|
|
loader.lazyRequireGetter(this, "clearTimeout", "Timer", true);
|
|
|
|
DevToolsUtils.defineLazyGetter(this, "nsFile", () => {
|
|
return CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
|
|
});
|
|
|
|
DevToolsUtils.defineLazyGetter(this, "socketTransportService", () => {
|
|
return Cc["@mozilla.org/network/socket-transport-service;1"]
|
|
.getService(Ci.nsISocketTransportService);
|
|
});
|
|
|
|
DevToolsUtils.defineLazyGetter(this, "certOverrideService", () => {
|
|
return Cc["@mozilla.org/security/certoverride;1"]
|
|
.getService(Ci.nsICertOverrideService);
|
|
});
|
|
|
|
DevToolsUtils.defineLazyGetter(this, "nssErrorsService", () => {
|
|
return Cc["@mozilla.org/nss_errors_service;1"]
|
|
.getService(Ci.nsINSSErrorsService);
|
|
});
|
|
|
|
DevToolsUtils.defineLazyModuleGetter(this, "Task",
|
|
"resource://gre/modules/Task.jsm");
|
|
|
|
var DebuggerSocket = {};
|
|
|
|
/**
|
|
* Connects to a debugger server socket.
|
|
*
|
|
* @param host string
|
|
* The host name or IP address of the debugger server.
|
|
* @param port number
|
|
* The port number of the debugger server.
|
|
* @param encryption boolean (optional)
|
|
* Whether the server requires encryption. Defaults to false.
|
|
* @param authenticator Authenticator (optional)
|
|
* |Authenticator| instance matching the mode in use by the server.
|
|
* Defaults to a PROMPT instance if not supplied.
|
|
* @param cert object (optional)
|
|
* The server's cert details. Used with OOB_CERT authentication.
|
|
* @return promise
|
|
* Resolved to a DebuggerTransport instance.
|
|
*/
|
|
DebuggerSocket.connect = Task.async(function*(settings) {
|
|
// Default to PROMPT |Authenticator| instance if not supplied
|
|
if (!settings.authenticator) {
|
|
settings.authenticator = new (Authenticators.get().Client)();
|
|
}
|
|
let { host, port, encryption, authenticator, cert } = settings;
|
|
let transport = yield _getTransport(settings);
|
|
yield authenticator.authenticate({
|
|
host,
|
|
port,
|
|
encryption,
|
|
cert,
|
|
transport
|
|
});
|
|
return transport;
|
|
});
|
|
|
|
/**
|
|
* Try very hard to create a DevTools transport, potentially making several
|
|
* connect attempts in the process.
|
|
*
|
|
* @param host string
|
|
* The host name or IP address of the debugger server.
|
|
* @param port number
|
|
* The port number of the debugger server.
|
|
* @param encryption boolean (optional)
|
|
* Whether the server requires encryption. Defaults to false.
|
|
* @param authenticator Authenticator
|
|
* |Authenticator| instance matching the mode in use by the server.
|
|
* Defaults to a PROMPT instance if not supplied.
|
|
* @param cert object (optional)
|
|
* The server's cert details. Used with OOB_CERT authentication.
|
|
* @return transport DebuggerTransport
|
|
* A possible DevTools transport (if connection succeeded and streams
|
|
* are actually alive and working)
|
|
* @return certError boolean
|
|
* Flag noting if cert trouble caused the streams to fail
|
|
* @return s nsISocketTransport
|
|
* Underlying socket transport, in case more details are needed.
|
|
*/
|
|
var _getTransport = Task.async(function*(settings) {
|
|
let { host, port, encryption } = settings;
|
|
let attempt = yield _attemptTransport(settings);
|
|
if (attempt.transport) {
|
|
return attempt.transport; // Success
|
|
}
|
|
|
|
// If the server cert failed validation, store a temporary override and make
|
|
// a second attempt.
|
|
if (encryption && attempt.certError) {
|
|
_storeCertOverride(attempt.s, host, port);
|
|
} else {
|
|
throw new Error("Connection failed");
|
|
}
|
|
|
|
attempt = yield _attemptTransport(settings);
|
|
if (attempt.transport) {
|
|
return attempt.transport; // Success
|
|
}
|
|
|
|
throw new Error("Connection failed even after cert override");
|
|
});
|
|
|
|
/**
|
|
* Make a single attempt to connect and create a DevTools transport. This could
|
|
* fail if the remote host is unreachable, for example. If there is security
|
|
* error due to the use of self-signed certs, you should make another attempt
|
|
* after storing a cert override.
|
|
*
|
|
* @param host string
|
|
* The host name or IP address of the debugger server.
|
|
* @param port number
|
|
* The port number of the debugger server.
|
|
* @param encryption boolean (optional)
|
|
* Whether the server requires encryption. Defaults to false.
|
|
* @param authenticator Authenticator
|
|
* |Authenticator| instance matching the mode in use by the server.
|
|
* Defaults to a PROMPT instance if not supplied.
|
|
* @param cert object (optional)
|
|
* The server's cert details. Used with OOB_CERT authentication.
|
|
* @return transport DebuggerTransport
|
|
* A possible DevTools transport (if connection succeeded and streams
|
|
* are actually alive and working)
|
|
* @return certError boolean
|
|
* Flag noting if cert trouble caused the streams to fail
|
|
* @return s nsISocketTransport
|
|
* Underlying socket transport, in case more details are needed.
|
|
*/
|
|
var _attemptTransport = Task.async(function*(settings) {
|
|
let { authenticator } = settings;
|
|
// _attemptConnect only opens the streams. Any failures at that stage
|
|
// aborts the connection process immedidately.
|
|
let { s, input, output } = yield _attemptConnect(settings);
|
|
|
|
// Check if the input stream is alive. If encryption is enabled, we need to
|
|
// watch out for cert errors by testing the input stream.
|
|
let alive, certError;
|
|
try {
|
|
let results = yield _isInputAlive(input);
|
|
alive = results.alive;
|
|
certError = results.certError;
|
|
} catch(e) {
|
|
// For other unexpected errors, like NS_ERROR_CONNECTION_REFUSED, we reach
|
|
// this block.
|
|
input.close();
|
|
output.close();
|
|
throw e;
|
|
}
|
|
dumpv("Server cert accepted? " + !certError);
|
|
|
|
// The |Authenticator| examines the connection as well and may determine it
|
|
// should be dropped.
|
|
alive = alive && authenticator.validateConnection({
|
|
host: settings.host,
|
|
port: settings.port,
|
|
encryption: settings.encryption,
|
|
cert: settings.cert,
|
|
socket: s
|
|
});
|
|
|
|
let transport;
|
|
if (alive) {
|
|
transport = new DebuggerTransport(input, output);
|
|
} else {
|
|
// Something went wrong, close the streams.
|
|
input.close();
|
|
output.close();
|
|
}
|
|
|
|
return { transport, certError, s };
|
|
});
|
|
|
|
/**
|
|
* Try to connect to a remote server socket.
|
|
*
|
|
* If successsful, the socket transport and its opened streams are returned.
|
|
* Typically, this will only fail if the host / port is unreachable. Other
|
|
* problems, such as security errors, will allow this stage to succeed, but then
|
|
* fail later when the streams are actually used.
|
|
* @return s nsISocketTransport
|
|
* Underlying socket transport, in case more details are needed.
|
|
* @return input nsIAsyncInputStream
|
|
* The socket's input stream.
|
|
* @return output nsIAsyncOutputStream
|
|
* The socket's output stream.
|
|
*/
|
|
var _attemptConnect = Task.async(function*({ host, port, encryption }) {
|
|
let s;
|
|
if (encryption) {
|
|
s = socketTransportService.createTransport(["ssl"], 1, host, port, null);
|
|
} else {
|
|
s = socketTransportService.createTransport(null, 0, host, port, null);
|
|
}
|
|
// By default the CONNECT socket timeout is very long, 65535 seconds,
|
|
// so that if we race to be in CONNECT state while the server socket is still
|
|
// initializing, the connection is stuck in connecting state for 18.20 hours!
|
|
s.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 2);
|
|
|
|
// If encrypting, load the client cert now, so we can deliver it at just the
|
|
// right time.
|
|
let clientCert;
|
|
if (encryption) {
|
|
clientCert = yield cert.local.getOrCreate();
|
|
}
|
|
|
|
let deferred = promise.defer();
|
|
let input;
|
|
let output;
|
|
// Delay opening the input stream until the transport has fully connected.
|
|
// The goal is to avoid showing the user a client cert UI prompt when
|
|
// encryption is used. This prompt is shown when the client opens the input
|
|
// stream and does not know which client cert to present to the server. To
|
|
// specify a client cert programmatically, we need to access the transport's
|
|
// nsISSLSocketControl interface, which is not accessible until the transport
|
|
// has connected.
|
|
s.setEventSink({
|
|
onTransportStatus(transport, status) {
|
|
if (status != Ci.nsISocketTransport.STATUS_CONNECTING_TO) {
|
|
return;
|
|
}
|
|
if (encryption) {
|
|
let sslSocketControl =
|
|
transport.securityInfo.QueryInterface(Ci.nsISSLSocketControl);
|
|
sslSocketControl.clientCert = clientCert;
|
|
}
|
|
try {
|
|
input = s.openInputStream(0, 0, 0);
|
|
} catch(e) {
|
|
deferred.reject(e);
|
|
}
|
|
deferred.resolve({ s, input, output });
|
|
}
|
|
}, Services.tm.currentThread);
|
|
|
|
// openOutputStream may throw NS_ERROR_NOT_INITIALIZED if we hit some race
|
|
// where the nsISocketTransport gets shutdown in between its instantiation and
|
|
// the call to this method.
|
|
try {
|
|
output = s.openOutputStream(0, 0, 0);
|
|
} catch(e) {
|
|
deferred.reject(e);
|
|
}
|
|
|
|
deferred.promise.catch(e => {
|
|
if (input) {
|
|
input.close();
|
|
}
|
|
if (output) {
|
|
output.close();
|
|
}
|
|
DevToolsUtils.reportException("_attemptConnect", e);
|
|
});
|
|
|
|
return deferred.promise;
|
|
});
|
|
|
|
/**
|
|
* Check if the input stream is alive. For an encrypted connection, it may not
|
|
* be if the client refuses the server's cert. A cert error is expected on
|
|
* first connection to a new host because the cert is self-signed.
|
|
*/
|
|
function _isInputAlive(input) {
|
|
let deferred = promise.defer();
|
|
input.asyncWait({
|
|
onInputStreamReady(stream) {
|
|
try {
|
|
stream.available();
|
|
deferred.resolve({ alive: true });
|
|
} catch (e) {
|
|
try {
|
|
// getErrorClass may throw if you pass a non-NSS error
|
|
let errorClass = nssErrorsService.getErrorClass(e.result);
|
|
if (errorClass === Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
|
|
deferred.resolve({ certError: true });
|
|
} else {
|
|
deferred.reject(e);
|
|
}
|
|
} catch (nssErr) {
|
|
deferred.reject(e);
|
|
}
|
|
}
|
|
}
|
|
}, 0, 0, Services.tm.currentThread);
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* To allow the connection to proceed with self-signed cert, we store a cert
|
|
* override. This implies that we take on the burden of authentication for
|
|
* these connections.
|
|
*/
|
|
function _storeCertOverride(s, host, port) {
|
|
let cert = s.securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
|
|
.SSLStatus.serverCert;
|
|
let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
|
|
Ci.nsICertOverrideService.ERROR_MISMATCH;
|
|
certOverrideService.rememberValidityOverride(host, port, cert, overrideBits,
|
|
true /* temporary */);
|
|
}
|
|
|
|
/**
|
|
* Creates a new socket listener for remote connections to the DebuggerServer.
|
|
* This helps contain and organize the parts of the server that may differ or
|
|
* are particular to one given listener mechanism vs. another.
|
|
*/
|
|
function SocketListener() {}
|
|
|
|
SocketListener.prototype = {
|
|
|
|
/* Socket Options */
|
|
|
|
/**
|
|
* The port or path to listen on.
|
|
*
|
|
* If given an integer, the port to listen on. Use -1 to choose any available
|
|
* port. Otherwise, the path to the unix socket domain file to listen on.
|
|
*/
|
|
portOrPath: null,
|
|
|
|
/**
|
|
* Controls whether this listener is announced via the service discovery
|
|
* mechanism.
|
|
*/
|
|
discoverable: false,
|
|
|
|
/**
|
|
* Controls whether this listener's transport uses encryption.
|
|
*/
|
|
encryption: false,
|
|
|
|
/**
|
|
* Controls the |Authenticator| used, which hooks various socket steps to
|
|
* implement an authentication policy. It is expected that different use
|
|
* cases may override pieces of the |Authenticator|. See auth.js.
|
|
*
|
|
* Here we set the default |Authenticator|, which is |Prompt|.
|
|
*/
|
|
authenticator: new (Authenticators.get().Server)(),
|
|
|
|
/**
|
|
* Validate that all options have been set to a supported configuration.
|
|
*/
|
|
_validateOptions: function() {
|
|
if (this.portOrPath === null) {
|
|
throw new Error("Must set a port / path to listen on.");
|
|
}
|
|
if (this.discoverable && !Number(this.portOrPath)) {
|
|
throw new Error("Discovery only supported for TCP sockets.");
|
|
}
|
|
this.authenticator.validateOptions(this);
|
|
},
|
|
|
|
/**
|
|
* Listens on the given port or socket file for remote debugger connections.
|
|
*/
|
|
open: function() {
|
|
this._validateOptions();
|
|
DebuggerServer._addListener(this);
|
|
|
|
let flags = Ci.nsIServerSocket.KeepWhenOffline;
|
|
// A preference setting can force binding on the loopback interface.
|
|
if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
|
|
flags |= Ci.nsIServerSocket.LoopbackOnly;
|
|
}
|
|
|
|
let self = this;
|
|
return Task.spawn(function*() {
|
|
let backlog = 4;
|
|
self._socket = self._createSocketInstance();
|
|
if (self.isPortBased) {
|
|
let port = Number(self.portOrPath);
|
|
self._socket.initSpecialConnection(port, flags, backlog);
|
|
} else {
|
|
let file = nsFile(self.portOrPath);
|
|
if (file.exists()) {
|
|
file.remove(false);
|
|
}
|
|
self._socket.initWithFilename(file, parseInt("666", 8), backlog);
|
|
}
|
|
yield self._setAdditionalSocketOptions();
|
|
self._socket.asyncListen(self);
|
|
dumpn("Socket listening on: " + (self.port || self.portOrPath));
|
|
}).then(() => {
|
|
this._advertise();
|
|
}).catch(e => {
|
|
dumpn("Could not start debugging listener on '" + this.portOrPath +
|
|
"': " + e);
|
|
this.close();
|
|
});
|
|
},
|
|
|
|
_advertise: function() {
|
|
if (!this.discoverable || !this.port) {
|
|
return;
|
|
}
|
|
|
|
let advertisement = {
|
|
port: this.port,
|
|
encryption: this.encryption,
|
|
};
|
|
|
|
this.authenticator.augmentAdvertisement(this, advertisement);
|
|
|
|
discovery.addService("devtools", advertisement);
|
|
},
|
|
|
|
_createSocketInstance: function() {
|
|
if (this.encryption) {
|
|
return Cc["@mozilla.org/network/tls-server-socket;1"]
|
|
.createInstance(Ci.nsITLSServerSocket);
|
|
}
|
|
return Cc["@mozilla.org/network/server-socket;1"]
|
|
.createInstance(Ci.nsIServerSocket);
|
|
},
|
|
|
|
_setAdditionalSocketOptions: Task.async(function*() {
|
|
if (this.encryption) {
|
|
this._socket.serverCert = yield cert.local.getOrCreate();
|
|
this._socket.setSessionCache(false);
|
|
this._socket.setSessionTickets(false);
|
|
let requestCert = Ci.nsITLSServerSocket.REQUEST_NEVER;
|
|
this._socket.setRequestClientCertificate(requestCert);
|
|
}
|
|
this.authenticator.augmentSocketOptions(this, this._socket);
|
|
}),
|
|
|
|
/**
|
|
* Closes the SocketListener. Notifies the server to remove the listener from
|
|
* the set of active SocketListeners.
|
|
*/
|
|
close: function() {
|
|
if (this.discoverable && this.port) {
|
|
discovery.removeService("devtools");
|
|
}
|
|
if (this._socket) {
|
|
this._socket.close();
|
|
this._socket = null;
|
|
}
|
|
DebuggerServer._removeListener(this);
|
|
},
|
|
|
|
get host() {
|
|
if (!this._socket) {
|
|
return null;
|
|
}
|
|
if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
|
|
return "127.0.0.1";
|
|
}
|
|
return "0.0.0.0";
|
|
},
|
|
|
|
/**
|
|
* Gets whether this listener uses a port number vs. a path.
|
|
*/
|
|
get isPortBased() {
|
|
return !!Number(this.portOrPath);
|
|
},
|
|
|
|
/**
|
|
* Gets the port that a TCP socket listener is listening on, or null if this
|
|
* is not a TCP socket (so there is no port).
|
|
*/
|
|
get port() {
|
|
if (!this.isPortBased || !this._socket) {
|
|
return null;
|
|
}
|
|
return this._socket.port;
|
|
},
|
|
|
|
get cert() {
|
|
if (!this._socket || !this._socket.serverCert) {
|
|
return null;
|
|
}
|
|
return {
|
|
sha256: this._socket.serverCert.sha256Fingerprint
|
|
};
|
|
},
|
|
|
|
// nsIServerSocketListener implementation
|
|
|
|
onSocketAccepted:
|
|
DevToolsUtils.makeInfallible(function(socket, socketTransport) {
|
|
new ServerSocketConnection(this, socketTransport);
|
|
}, "SocketListener.onSocketAccepted"),
|
|
|
|
onStopListening: function(socket, status) {
|
|
dumpn("onStopListening, status: " + status);
|
|
}
|
|
|
|
};
|
|
|
|
// Client must complete TLS handshake within this window (ms)
|
|
loader.lazyGetter(this, "HANDSHAKE_TIMEOUT", () => {
|
|
return Services.prefs.getIntPref("devtools.remote.tls-handshake-timeout");
|
|
});
|
|
|
|
/**
|
|
* A |ServerSocketConnection| is created by a |SocketListener| for each accepted
|
|
* incoming socket. This is a short-lived object used to implement
|
|
* authentication and verify encryption prior to handing off the connection to
|
|
* the |DebuggerServer|.
|
|
*/
|
|
function ServerSocketConnection(listener, socketTransport) {
|
|
this._listener = listener;
|
|
this._socketTransport = socketTransport;
|
|
this._handle();
|
|
}
|
|
|
|
ServerSocketConnection.prototype = {
|
|
|
|
get authentication() {
|
|
return this._listener.authenticator.mode;
|
|
},
|
|
|
|
get host() {
|
|
return this._socketTransport.host;
|
|
},
|
|
|
|
get port() {
|
|
return this._socketTransport.port;
|
|
},
|
|
|
|
get cert() {
|
|
if (!this._clientCert) {
|
|
return null;
|
|
}
|
|
return {
|
|
sha256: this._clientCert.sha256Fingerprint
|
|
};
|
|
},
|
|
|
|
get address() {
|
|
return this.host + ":" + this.port;
|
|
},
|
|
|
|
get client() {
|
|
let client = {
|
|
host: this.host,
|
|
port: this.port
|
|
};
|
|
if (this.cert) {
|
|
client.cert = this.cert;
|
|
}
|
|
return client;
|
|
},
|
|
|
|
get server() {
|
|
let server = {
|
|
host: this._listener.host,
|
|
port: this._listener.port
|
|
};
|
|
if (this._listener.cert) {
|
|
server.cert = this._listener.cert;
|
|
}
|
|
return server;
|
|
},
|
|
|
|
/**
|
|
* This is the main authentication workflow. If any pieces reject a promise,
|
|
* the connection is denied. If the entire process resolves successfully,
|
|
* the connection is finally handed off to the |DebuggerServer|.
|
|
*/
|
|
_handle() {
|
|
dumpn("Debugging connection starting authentication on " + this.address);
|
|
let self = this;
|
|
Task.spawn(function*() {
|
|
self._listenForTLSHandshake();
|
|
self._createTransport();
|
|
yield self._awaitTLSHandshake();
|
|
yield self._authenticate();
|
|
}).then(() => this.allow()).catch(e => this.deny(e));
|
|
},
|
|
|
|
/**
|
|
* We need to open the streams early on, as that is required in the case of
|
|
* TLS sockets to keep the handshake moving.
|
|
*/
|
|
_createTransport() {
|
|
let input = this._socketTransport.openInputStream(0, 0, 0);
|
|
let output = this._socketTransport.openOutputStream(0, 0, 0);
|
|
this._transport = new DebuggerTransport(input, output);
|
|
// Start up the transport to observe the streams in case they are closed
|
|
// early. This allows us to clean up our state as well.
|
|
this._transport.hooks = {
|
|
onClosed: reason => {
|
|
this.deny(reason);
|
|
}
|
|
};
|
|
this._transport.ready();
|
|
},
|
|
|
|
/**
|
|
* Set the socket's security observer, which receives an event via the
|
|
* |onHandshakeDone| callback when the TLS handshake completes.
|
|
*/
|
|
_setSecurityObserver(observer) {
|
|
if (!this._socketTransport || !this._socketTransport.securityInfo) {
|
|
return;
|
|
}
|
|
let connectionInfo = this._socketTransport.securityInfo
|
|
.QueryInterface(Ci.nsITLSServerConnectionInfo);
|
|
connectionInfo.setSecurityObserver(observer);
|
|
},
|
|
|
|
/**
|
|
* When encryption is used, we wait for the client to complete the TLS
|
|
* handshake before proceeding. The handshake details are validated in
|
|
* |onHandshakeDone|.
|
|
*/
|
|
_listenForTLSHandshake() {
|
|
this._handshakeDeferred = promise.defer();
|
|
if (!this._listener.encryption) {
|
|
this._handshakeDeferred.resolve();
|
|
return;
|
|
}
|
|
this._setSecurityObserver(this);
|
|
this._handshakeTimeout = setTimeout(this._onHandshakeTimeout.bind(this),
|
|
HANDSHAKE_TIMEOUT);
|
|
},
|
|
|
|
_awaitTLSHandshake() {
|
|
return this._handshakeDeferred.promise;
|
|
},
|
|
|
|
_onHandshakeTimeout() {
|
|
dumpv("Client failed to complete TLS handshake");
|
|
this._handshakeDeferred.reject(Cr.NS_ERROR_NET_TIMEOUT);
|
|
},
|
|
|
|
// nsITLSServerSecurityObserver implementation
|
|
onHandshakeDone(socket, clientStatus) {
|
|
clearTimeout(this._handshakeTimeout);
|
|
this._setSecurityObserver(null);
|
|
dumpv("TLS version: " + clientStatus.tlsVersionUsed.toString(16));
|
|
dumpv("TLS cipher: " + clientStatus.cipherName);
|
|
dumpv("TLS key length: " + clientStatus.keyLength);
|
|
dumpv("TLS MAC length: " + clientStatus.macLength);
|
|
this._clientCert = clientStatus.peerCert;
|
|
/*
|
|
* TODO: These rules should be really be set on the TLS socket directly, but
|
|
* this would need more platform work to expose it via XPCOM.
|
|
*
|
|
* Enforcing cipher suites here would be a bad idea, as we want TLS
|
|
* cipher negotiation to work correctly. The server already allows only
|
|
* Gecko's normal set of cipher suites.
|
|
*/
|
|
if (clientStatus.tlsVersionUsed < Ci.nsITLSClientStatus.TLS_VERSION_1_2) {
|
|
this._handshakeDeferred.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
|
|
return;
|
|
}
|
|
|
|
this._handshakeDeferred.resolve();
|
|
},
|
|
|
|
_authenticate: Task.async(function*() {
|
|
let result = yield this._listener.authenticator.authenticate({
|
|
client: this.client,
|
|
server: this.server,
|
|
transport: this._transport
|
|
});
|
|
switch (result) {
|
|
case AuthenticationResult.DISABLE_ALL:
|
|
DebuggerServer.closeAllListeners();
|
|
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", false);
|
|
return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
|
|
case AuthenticationResult.DENY:
|
|
return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
|
|
case AuthenticationResult.ALLOW:
|
|
case AuthenticationResult.ALLOW_PERSIST:
|
|
return promise.resolve();
|
|
default:
|
|
return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
|
|
}
|
|
}),
|
|
|
|
deny(result) {
|
|
if (this._destroyed) {
|
|
return;
|
|
}
|
|
let errorName = result;
|
|
for (let name in Cr) {
|
|
if (Cr[name] === result) {
|
|
errorName = name;
|
|
break;
|
|
}
|
|
}
|
|
dumpn("Debugging connection denied on " + this.address +
|
|
" (" + errorName + ")");
|
|
this._transport.hooks = null;
|
|
this._transport.close(result);
|
|
this._socketTransport.close(result);
|
|
this.destroy();
|
|
},
|
|
|
|
allow() {
|
|
if (this._destroyed) {
|
|
return;
|
|
}
|
|
dumpn("Debugging connection allowed on " + this.address);
|
|
DebuggerServer._onConnection(this._transport);
|
|
this.destroy();
|
|
},
|
|
|
|
destroy() {
|
|
this._destroyed = true;
|
|
clearTimeout(this._handshakeTimeout);
|
|
this._setSecurityObserver(null);
|
|
this._listener = null;
|
|
this._socketTransport = null;
|
|
this._transport = null;
|
|
this._clientCert = null;
|
|
}
|
|
|
|
};
|
|
|
|
DebuggerSocket.createListener = function() {
|
|
return new SocketListener();
|
|
};
|
|
|
|
exports.DebuggerSocket = DebuggerSocket;
|