Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 "use strict";
8 var Ci = Components.interfaces;
9 var Cc = Components.classes;
10 var Cu = Components.utils;
11 var Cr = Components.results;
12 // On B2G scope object misbehaves and we have to bind globals to `this`
13 // in order to ensure theses variable to be visible in transport.js
14 this.Ci = Ci;
15 this.Cc = Cc;
16 this.Cu = Cu;
17 this.Cr = Cr;
19 this.EXPORTED_SYMBOLS = ["DebuggerTransport",
20 "DebuggerClient",
21 "RootClient",
22 "debuggerSocketConnect",
23 "LongStringClient",
24 "EnvironmentClient",
25 "ObjectClient"];
27 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
28 Cu.import("resource://gre/modules/NetUtil.jsm");
29 Cu.import("resource://gre/modules/Services.jsm");
30 Cu.import("resource://gre/modules/Timer.jsm");
32 let promise = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js").Promise;
33 const { defer, resolve, reject } = promise;
35 XPCOMUtils.defineLazyServiceGetter(this, "socketTransportService",
36 "@mozilla.org/network/socket-transport-service;1",
37 "nsISocketTransportService");
39 XPCOMUtils.defineLazyModuleGetter(this, "console",
40 "resource://gre/modules/devtools/Console.jsm");
42 XPCOMUtils.defineLazyModuleGetter(this, "devtools",
43 "resource://gre/modules/devtools/Loader.jsm");
45 Object.defineProperty(this, "WebConsoleClient", {
46 get: function () {
47 return devtools.require("devtools/toolkit/webconsole/client").WebConsoleClient;
48 },
49 configurable: true,
50 enumerable: true
51 });
53 Components.utils.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
54 this.makeInfallible = DevToolsUtils.makeInfallible;
56 let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
58 function dumpn(str)
59 {
60 if (wantLogging) {
61 dump("DBG-CLIENT: " + str + "\n");
62 }
63 }
65 let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
66 .getService(Ci.mozIJSSubScriptLoader);
67 loader.loadSubScript("resource://gre/modules/devtools/server/transport.js", this);
69 /**
70 * Add simple event notification to a prototype object. Any object that has
71 * some use for event notifications or the observer pattern in general can be
72 * augmented with the necessary facilities by passing its prototype to this
73 * function.
74 *
75 * @param aProto object
76 * The prototype object that will be modified.
77 */
78 function eventSource(aProto) {
79 /**
80 * Add a listener to the event source for a given event.
81 *
82 * @param aName string
83 * The event to listen for.
84 * @param aListener function
85 * Called when the event is fired. If the same listener
86 * is added more than once, it will be called once per
87 * addListener call.
88 */
89 aProto.addListener = function (aName, aListener) {
90 if (typeof aListener != "function") {
91 throw TypeError("Listeners must be functions.");
92 }
94 if (!this._listeners) {
95 this._listeners = {};
96 }
98 this._getListeners(aName).push(aListener);
99 };
101 /**
102 * Add a listener to the event source for a given event. The
103 * listener will be removed after it is called for the first time.
104 *
105 * @param aName string
106 * The event to listen for.
107 * @param aListener function
108 * Called when the event is fired.
109 */
110 aProto.addOneTimeListener = function (aName, aListener) {
111 let l = (...args) => {
112 this.removeListener(aName, l);
113 aListener.apply(null, args);
114 };
115 this.addListener(aName, l);
116 };
118 /**
119 * Remove a listener from the event source previously added with
120 * addListener().
121 *
122 * @param aName string
123 * The event name used during addListener to add the listener.
124 * @param aListener function
125 * The callback to remove. If addListener was called multiple
126 * times, all instances will be removed.
127 */
128 aProto.removeListener = function (aName, aListener) {
129 if (!this._listeners || !this._listeners[aName]) {
130 return;
131 }
132 this._listeners[aName] =
133 this._listeners[aName].filter(function (l) { return l != aListener });
134 };
136 /**
137 * Returns the listeners for the specified event name. If none are defined it
138 * initializes an empty list and returns that.
139 *
140 * @param aName string
141 * The event name.
142 */
143 aProto._getListeners = function (aName) {
144 if (aName in this._listeners) {
145 return this._listeners[aName];
146 }
147 this._listeners[aName] = [];
148 return this._listeners[aName];
149 };
151 /**
152 * Notify listeners of an event.
153 *
154 * @param aName string
155 * The event to fire.
156 * @param arguments
157 * All arguments will be passed along to the listeners,
158 * including the name argument.
159 */
160 aProto.notify = function () {
161 if (!this._listeners) {
162 return;
163 }
165 let name = arguments[0];
166 let listeners = this._getListeners(name).slice(0);
168 for each (let listener in listeners) {
169 try {
170 listener.apply(null, arguments);
171 } catch (e) {
172 // Prevent a bad listener from interfering with the others.
173 DevToolsUtils.reportException("notify event '" + name + "'", e);
174 }
175 }
176 }
177 }
179 /**
180 * Set of protocol messages that affect thread state, and the
181 * state the actor is in after each message.
182 */
183 const ThreadStateTypes = {
184 "paused": "paused",
185 "resumed": "attached",
186 "detached": "detached"
187 };
189 /**
190 * Set of protocol messages that are sent by the server without a prior request
191 * by the client.
192 */
193 const UnsolicitedNotifications = {
194 "consoleAPICall": "consoleAPICall",
195 "eventNotification": "eventNotification",
196 "fileActivity": "fileActivity",
197 "lastPrivateContextExited": "lastPrivateContextExited",
198 "logMessage": "logMessage",
199 "networkEvent": "networkEvent",
200 "networkEventUpdate": "networkEventUpdate",
201 "newGlobal": "newGlobal",
202 "newScript": "newScript",
203 "newSource": "newSource",
204 "tabDetached": "tabDetached",
205 "tabListChanged": "tabListChanged",
206 "reflowActivity": "reflowActivity",
207 "addonListChanged": "addonListChanged",
208 "tabNavigated": "tabNavigated",
209 "pageError": "pageError",
210 "documentLoad": "documentLoad",
211 "enteredFrame": "enteredFrame",
212 "exitedFrame": "exitedFrame",
213 "appOpen": "appOpen",
214 "appClose": "appClose",
215 "appInstall": "appInstall",
216 "appUninstall": "appUninstall"
217 };
219 /**
220 * Set of pause types that are sent by the server and not as an immediate
221 * response to a client request.
222 */
223 const UnsolicitedPauses = {
224 "resumeLimit": "resumeLimit",
225 "debuggerStatement": "debuggerStatement",
226 "breakpoint": "breakpoint",
227 "DOMEvent": "DOMEvent",
228 "watchpoint": "watchpoint",
229 "exception": "exception"
230 };
232 /**
233 * Creates a client for the remote debugging protocol server. This client
234 * provides the means to communicate with the server and exchange the messages
235 * required by the protocol in a traditional JavaScript API.
236 */
237 this.DebuggerClient = function (aTransport)
238 {
239 this._transport = aTransport;
240 this._transport.hooks = this;
242 // Map actor ID to client instance for each actor type.
243 this._threadClients = new Map;
244 this._addonClients = new Map;
245 this._tabClients = new Map;
246 this._tracerClients = new Map;
247 this._consoleClients = new Map;
249 this._pendingRequests = [];
250 this._activeRequests = new Map;
251 this._eventsEnabled = true;
253 this.compat = new ProtocolCompatibility(this, []);
254 this.traits = {};
256 this.request = this.request.bind(this);
257 this.localTransport = this._transport.onOutputStreamReady === undefined;
259 /*
260 * As the first thing on the connection, expect a greeting packet from
261 * the connection's root actor.
262 */
263 this.mainRoot = null;
264 this.expectReply("root", (aPacket) => {
265 this.mainRoot = new RootClient(this, aPacket);
266 this.notify("connected", aPacket.applicationType, aPacket.traits);
267 });
268 }
270 /**
271 * A declarative helper for defining methods that send requests to the server.
272 *
273 * @param aPacketSkeleton
274 * The form of the packet to send. Can specify fields to be filled from
275 * the parameters by using the |args| function.
276 * @param telemetry
277 * The unique suffix of the telemetry histogram id.
278 * @param before
279 * The function to call before sending the packet. Is passed the packet,
280 * and the return value is used as the new packet. The |this| context is
281 * the instance of the client object we are defining a method for.
282 * @param after
283 * The function to call after the response is received. It is passed the
284 * response, and the return value is considered the new response that
285 * will be passed to the callback. The |this| context is the instance of
286 * the client object we are defining a method for.
287 */
288 DebuggerClient.requester = function (aPacketSkeleton,
289 { telemetry, before, after }) {
290 return DevToolsUtils.makeInfallible(function (...args) {
291 let histogram, startTime;
292 if (telemetry) {
293 let transportType = this._transport.onOutputStreamReady === undefined
294 ? "LOCAL_"
295 : "REMOTE_";
296 let histogramId = "DEVTOOLS_DEBUGGER_RDP_"
297 + transportType + telemetry + "_MS";
298 histogram = Services.telemetry.getHistogramById(histogramId);
299 startTime = +new Date;
300 }
301 let outgoingPacket = {
302 to: aPacketSkeleton.to || this.actor
303 };
305 let maxPosition = -1;
306 for (let k of Object.keys(aPacketSkeleton)) {
307 if (aPacketSkeleton[k] instanceof DebuggerClient.Argument) {
308 let { position } = aPacketSkeleton[k];
309 outgoingPacket[k] = aPacketSkeleton[k].getArgument(args);
310 maxPosition = Math.max(position, maxPosition);
311 } else {
312 outgoingPacket[k] = aPacketSkeleton[k];
313 }
314 }
316 if (before) {
317 outgoingPacket = before.call(this, outgoingPacket);
318 }
320 this.request(outgoingPacket, DevToolsUtils.makeInfallible(function (aResponse) {
321 if (after) {
322 let { from } = aResponse;
323 aResponse = after.call(this, aResponse);
324 if (!aResponse.from) {
325 aResponse.from = from;
326 }
327 }
329 // The callback is always the last parameter.
330 let thisCallback = args[maxPosition + 1];
331 if (thisCallback) {
332 thisCallback(aResponse);
333 }
335 if (histogram) {
336 histogram.add(+new Date - startTime);
337 }
338 }.bind(this), "DebuggerClient.requester request callback"));
340 }, "DebuggerClient.requester");
341 };
343 function args(aPos) {
344 return new DebuggerClient.Argument(aPos);
345 }
347 DebuggerClient.Argument = function (aPosition) {
348 this.position = aPosition;
349 };
351 DebuggerClient.Argument.prototype.getArgument = function (aParams) {
352 if (!(this.position in aParams)) {
353 throw new Error("Bad index into params: " + this.position);
354 }
355 return aParams[this.position];
356 };
358 DebuggerClient.prototype = {
359 /**
360 * Connect to the server and start exchanging protocol messages.
361 *
362 * @param aOnConnected function
363 * If specified, will be called when the greeting packet is
364 * received from the debugging server.
365 */
366 connect: function (aOnConnected) {
367 this.addOneTimeListener("connected", (aName, aApplicationType, aTraits) => {
368 this.traits = aTraits;
369 if (aOnConnected) {
370 aOnConnected(aApplicationType, aTraits);
371 }
372 });
374 this._transport.ready();
375 },
377 /**
378 * Shut down communication with the debugging server.
379 *
380 * @param aOnClosed function
381 * If specified, will be called when the debugging connection
382 * has been closed.
383 */
384 close: function (aOnClosed) {
385 // Disable detach event notifications, because event handlers will be in a
386 // cleared scope by the time they run.
387 this._eventsEnabled = false;
389 if (aOnClosed) {
390 this.addOneTimeListener('closed', function (aEvent) {
391 aOnClosed();
392 });
393 }
395 const detachClients = (clientMap, next) => {
396 const clients = clientMap.values();
397 const total = clientMap.size;
398 let numFinished = 0;
400 if (total == 0) {
401 next();
402 return;
403 }
405 for (let client of clients) {
406 let method = client instanceof WebConsoleClient ? "close" : "detach";
407 client[method](() => {
408 if (++numFinished === total) {
409 clientMap.clear();
410 next();
411 }
412 });
413 }
414 };
416 detachClients(this._consoleClients, () => {
417 detachClients(this._threadClients, () => {
418 detachClients(this._tabClients, () => {
419 detachClients(this._addonClients, () => {
420 this._transport.close();
421 this._transport = null;
422 });
423 });
424 });
425 });
426 },
428 /*
429 * This function exists only to preserve DebuggerClient's interface;
430 * new code should say 'client.mainRoot.listTabs()'.
431 */
432 listTabs: function (aOnResponse) { return this.mainRoot.listTabs(aOnResponse); },
434 /*
435 * This function exists only to preserve DebuggerClient's interface;
436 * new code should say 'client.mainRoot.listAddons()'.
437 */
438 listAddons: function (aOnResponse) { return this.mainRoot.listAddons(aOnResponse); },
440 /**
441 * Attach to a tab actor.
442 *
443 * @param string aTabActor
444 * The actor ID for the tab to attach.
445 * @param function aOnResponse
446 * Called with the response packet and a TabClient
447 * (which will be undefined on error).
448 */
449 attachTab: function (aTabActor, aOnResponse) {
450 if (this._tabClients.has(aTabActor)) {
451 let cachedTab = this._tabClients.get(aTabActor);
452 let cachedResponse = {
453 cacheEnabled: cachedTab.cacheEnabled,
454 javascriptEnabled: cachedTab.javascriptEnabled,
455 traits: cachedTab.traits,
456 };
457 setTimeout(() => aOnResponse(cachedResponse, cachedTab), 0);
458 return;
459 }
461 let packet = {
462 to: aTabActor,
463 type: "attach"
464 };
465 this.request(packet, (aResponse) => {
466 let tabClient;
467 if (!aResponse.error) {
468 tabClient = new TabClient(this, aResponse);
469 this._tabClients.set(aTabActor, tabClient);
470 }
471 aOnResponse(aResponse, tabClient);
472 });
473 },
475 /**
476 * Attach to an addon actor.
477 *
478 * @param string aAddonActor
479 * The actor ID for the addon to attach.
480 * @param function aOnResponse
481 * Called with the response packet and a AddonClient
482 * (which will be undefined on error).
483 */
484 attachAddon: function DC_attachAddon(aAddonActor, aOnResponse) {
485 let packet = {
486 to: aAddonActor,
487 type: "attach"
488 };
489 this.request(packet, aResponse => {
490 let addonClient;
491 if (!aResponse.error) {
492 addonClient = new AddonClient(this, aAddonActor);
493 this._addonClients[aAddonActor] = addonClient;
494 this.activeAddon = addonClient;
495 }
496 aOnResponse(aResponse, addonClient);
497 });
498 },
500 /**
501 * Attach to a Web Console actor.
502 *
503 * @param string aConsoleActor
504 * The ID for the console actor to attach to.
505 * @param array aListeners
506 * The console listeners you want to start.
507 * @param function aOnResponse
508 * Called with the response packet and a WebConsoleClient
509 * instance (which will be undefined on error).
510 */
511 attachConsole:
512 function (aConsoleActor, aListeners, aOnResponse) {
513 let packet = {
514 to: aConsoleActor,
515 type: "startListeners",
516 listeners: aListeners,
517 };
519 this.request(packet, (aResponse) => {
520 let consoleClient;
521 if (!aResponse.error) {
522 if (this._consoleClients.has(aConsoleActor)) {
523 consoleClient = this._consoleClients.get(aConsoleActor);
524 } else {
525 consoleClient = new WebConsoleClient(this, aResponse);
526 this._consoleClients.set(aConsoleActor, consoleClient);
527 }
528 }
529 aOnResponse(aResponse, consoleClient);
530 });
531 },
533 /**
534 * Attach to a global-scoped thread actor for chrome debugging.
535 *
536 * @param string aThreadActor
537 * The actor ID for the thread to attach.
538 * @param function aOnResponse
539 * Called with the response packet and a ThreadClient
540 * (which will be undefined on error).
541 * @param object aOptions
542 * Configuration options.
543 * - useSourceMaps: whether to use source maps or not.
544 */
545 attachThread: function (aThreadActor, aOnResponse, aOptions={}) {
546 if (this._threadClients.has(aThreadActor)) {
547 setTimeout(() => aOnResponse({}, this._threadClients.get(aThreadActor)), 0);
548 return;
549 }
551 let packet = {
552 to: aThreadActor,
553 type: "attach",
554 options: aOptions
555 };
556 this.request(packet, (aResponse) => {
557 if (!aResponse.error) {
558 var threadClient = new ThreadClient(this, aThreadActor);
559 this._threadClients.set(aThreadActor, threadClient);
560 }
561 aOnResponse(aResponse, threadClient);
562 });
563 },
565 /**
566 * Attach to a trace actor.
567 *
568 * @param string aTraceActor
569 * The actor ID for the tracer to attach.
570 * @param function aOnResponse
571 * Called with the response packet and a TraceClient
572 * (which will be undefined on error).
573 */
574 attachTracer: function (aTraceActor, aOnResponse) {
575 if (this._tracerClients.has(aTraceActor)) {
576 setTimeout(() => aOnResponse({}, this._tracerClients.get(aTraceActor)), 0);
577 return;
578 }
580 let packet = {
581 to: aTraceActor,
582 type: "attach"
583 };
584 this.request(packet, (aResponse) => {
585 if (!aResponse.error) {
586 var traceClient = new TraceClient(this, aTraceActor);
587 this._tracerClients.set(aTraceActor, traceClient);
588 }
589 aOnResponse(aResponse, traceClient);
590 });
591 },
593 /**
594 * Release an object actor.
595 *
596 * @param string aActor
597 * The actor ID to send the request to.
598 * @param aOnResponse function
599 * If specified, will be called with the response packet when
600 * debugging server responds.
601 */
602 release: DebuggerClient.requester({
603 to: args(0),
604 type: "release"
605 }, {
606 telemetry: "RELEASE"
607 }),
609 /**
610 * Send a request to the debugging server.
611 *
612 * @param aRequest object
613 * A JSON packet to send to the debugging server.
614 * @param aOnResponse function
615 * If specified, will be called with the response packet when
616 * debugging server responds.
617 */
618 request: function (aRequest, aOnResponse) {
619 if (!this.mainRoot) {
620 throw Error("Have not yet received a hello packet from the server.");
621 }
622 if (!aRequest.to) {
623 let type = aRequest.type || "";
624 throw Error("'" + type + "' request packet has no destination.");
625 }
627 this._pendingRequests.push({ to: aRequest.to,
628 request: aRequest,
629 onResponse: aOnResponse });
630 this._sendRequests();
631 },
633 /**
634 * Send pending requests to any actors that don't already have an
635 * active request.
636 */
637 _sendRequests: function () {
638 this._pendingRequests = this._pendingRequests.filter((request) => {
639 if (this._activeRequests.has(request.to)) {
640 return true;
641 }
643 this.expectReply(request.to, request.onResponse);
644 this._transport.send(request.request);
646 return false;
647 });
648 },
650 /**
651 * Arrange to hand the next reply from |aActor| to |aHandler|.
652 *
653 * DebuggerClient.prototype.request usually takes care of establishing
654 * the handler for a given request, but in rare cases (well, greetings
655 * from new root actors, is the only case at the moment) we must be
656 * prepared for a "reply" that doesn't correspond to any request we sent.
657 */
658 expectReply: function (aActor, aHandler) {
659 if (this._activeRequests.has(aActor)) {
660 throw Error("clashing handlers for next reply from " + uneval(aActor));
661 }
662 this._activeRequests.set(aActor, aHandler);
663 },
665 // Transport hooks.
667 /**
668 * Called by DebuggerTransport to dispatch incoming packets as appropriate.
669 *
670 * @param aPacket object
671 * The incoming packet.
672 * @param aIgnoreCompatibility boolean
673 * Set true to not pass the packet through the compatibility layer.
674 */
675 onPacket: function (aPacket, aIgnoreCompatibility=false) {
676 let packet = aIgnoreCompatibility
677 ? aPacket
678 : this.compat.onPacket(aPacket);
680 resolve(packet).then(aPacket => {
681 if (!aPacket.from) {
682 DevToolsUtils.reportException(
683 "onPacket",
684 new Error("Server did not specify an actor, dropping packet: " +
685 JSON.stringify(aPacket)));
686 return;
687 }
689 // If we have a registered Front for this actor, let it handle the packet
690 // and skip all the rest of this unpleasantness.
691 let front = this.getActor(aPacket.from);
692 if (front) {
693 front.onPacket(aPacket);
694 return;
695 }
697 let onResponse;
698 // See if we have a handler function waiting for a reply from this
699 // actor. (Don't count unsolicited notifications or pauses as
700 // replies.)
701 if (this._activeRequests.has(aPacket.from) &&
702 !(aPacket.type in UnsolicitedNotifications) &&
703 !(aPacket.type == ThreadStateTypes.paused &&
704 aPacket.why.type in UnsolicitedPauses)) {
705 onResponse = this._activeRequests.get(aPacket.from);
706 this._activeRequests.delete(aPacket.from);
707 }
709 // Packets that indicate thread state changes get special treatment.
710 if (aPacket.type in ThreadStateTypes &&
711 this._threadClients.has(aPacket.from)) {
712 this._threadClients.get(aPacket.from)._onThreadState(aPacket);
713 }
714 // On navigation the server resumes, so the client must resume as well.
715 // We achieve that by generating a fake resumption packet that triggers
716 // the client's thread state change listeners.
717 if (aPacket.type == UnsolicitedNotifications.tabNavigated &&
718 this._tabClients.has(aPacket.from) &&
719 this._tabClients.get(aPacket.from).thread) {
720 let thread = this._tabClients.get(aPacket.from).thread;
721 let resumption = { from: thread._actor, type: "resumed" };
722 thread._onThreadState(resumption);
723 }
724 // Only try to notify listeners on events, not responses to requests
725 // that lack a packet type.
726 if (aPacket.type) {
727 this.notify(aPacket.type, aPacket);
728 }
730 if (onResponse) {
731 onResponse(aPacket);
732 }
734 this._sendRequests();
735 }, ex => DevToolsUtils.reportException("onPacket handler", ex));
736 },
738 /**
739 * Called by DebuggerTransport when the underlying stream is closed.
740 *
741 * @param aStatus nsresult
742 * The status code that corresponds to the reason for closing
743 * the stream.
744 */
745 onClosed: function (aStatus) {
746 this.notify("closed");
747 },
749 /**
750 * Actor lifetime management, echos the server's actor pools.
751 */
752 __pools: null,
753 get _pools() {
754 if (this.__pools) {
755 return this.__pools;
756 }
757 this.__pools = new Set();
758 return this.__pools;
759 },
761 addActorPool: function (pool) {
762 this._pools.add(pool);
763 },
764 removeActorPool: function (pool) {
765 this._pools.delete(pool);
766 },
767 getActor: function (actorID) {
768 let pool = this.poolFor(actorID);
769 return pool ? pool.get(actorID) : null;
770 },
772 poolFor: function (actorID) {
773 for (let pool of this._pools) {
774 if (pool.has(actorID)) return pool;
775 }
776 return null;
777 },
779 /**
780 * Currently attached addon.
781 */
782 activeAddon: null
783 }
785 eventSource(DebuggerClient.prototype);
787 // Constants returned by `FeatureCompatibilityShim.onPacketTest`.
788 const SUPPORTED = 1;
789 const NOT_SUPPORTED = 2;
790 const SKIP = 3;
792 /**
793 * This object provides an abstraction layer over all of our backwards
794 * compatibility, feature detection, and shimming with regards to the remote
795 * debugging prototcol.
796 *
797 * @param aFeatures Array
798 * An array of FeatureCompatibilityShim objects
799 */
800 function ProtocolCompatibility(aClient, aFeatures) {
801 this._client = aClient;
802 this._featuresWithUnknownSupport = new Set(aFeatures);
803 this._featuresWithoutSupport = new Set();
805 this._featureDeferreds = Object.create(null)
806 for (let f of aFeatures) {
807 this._featureDeferreds[f.name] = defer();
808 }
809 }
811 ProtocolCompatibility.prototype = {
812 /**
813 * Returns a promise that resolves to true if the RDP supports the feature,
814 * and is rejected otherwise.
815 *
816 * @param aFeatureName String
817 * The name of the feature we are testing.
818 */
819 supportsFeature: function (aFeatureName) {
820 return this._featureDeferreds[aFeatureName].promise;
821 },
823 /**
824 * Force a feature to be considered unsupported.
825 *
826 * @param aFeatureName String
827 * The name of the feature we are testing.
828 */
829 rejectFeature: function (aFeatureName) {
830 this._featureDeferreds[aFeatureName].reject(false);
831 },
833 /**
834 * Called for each packet received over the RDP from the server. Tests for
835 * protocol features and shims packets to support needed features.
836 *
837 * @param aPacket Object
838 * Packet received over the RDP from the server.
839 */
840 onPacket: function (aPacket) {
841 this._detectFeatures(aPacket);
842 return this._shimPacket(aPacket);
843 },
845 /**
846 * For each of the features we don't know whether the server supports or not,
847 * attempt to detect support based on the packet we just received.
848 */
849 _detectFeatures: function (aPacket) {
850 for (let feature of this._featuresWithUnknownSupport) {
851 try {
852 switch (feature.onPacketTest(aPacket)) {
853 case SKIP:
854 break;
855 case SUPPORTED:
856 this._featuresWithUnknownSupport.delete(feature);
857 this._featureDeferreds[feature.name].resolve(true);
858 break;
859 case NOT_SUPPORTED:
860 this._featuresWithUnknownSupport.delete(feature);
861 this._featuresWithoutSupport.add(feature);
862 this.rejectFeature(feature.name);
863 break;
864 default:
865 DevToolsUtils.reportException(
866 "PC__detectFeatures",
867 new Error("Bad return value from `onPacketTest` for feature '"
868 + feature.name + "'"));
869 }
870 } catch (ex) {
871 DevToolsUtils.reportException("PC__detectFeatures", ex);
872 }
873 }
874 },
876 /**
877 * Go through each of the features that we know are unsupported by the current
878 * server and attempt to shim support.
879 */
880 _shimPacket: function (aPacket) {
881 let extraPackets = [];
883 let loop = function (aFeatures, aPacket) {
884 if (aFeatures.length === 0) {
885 for (let packet of extraPackets) {
886 this._client.onPacket(packet, true);
887 }
888 return aPacket;
889 } else {
890 let replacePacket = function (aNewPacket) {
891 return aNewPacket;
892 };
893 let extraPacket = function (aExtraPacket) {
894 extraPackets.push(aExtraPacket);
895 return aPacket;
896 };
897 let keepPacket = function () {
898 return aPacket;
899 };
900 let newPacket = aFeatures[0].translatePacket(aPacket,
901 replacePacket,
902 extraPacket,
903 keepPacket);
904 return resolve(newPacket).then(loop.bind(null, aFeatures.slice(1)));
905 }
906 }.bind(this);
908 return loop([f for (f of this._featuresWithoutSupport)],
909 aPacket);
910 }
911 };
913 /**
914 * Interface defining what methods a feature compatibility shim should have.
915 */
916 const FeatureCompatibilityShim = {
917 // The name of the feature
918 name: null,
920 /**
921 * Takes a packet and returns boolean (or promise of boolean) indicating
922 * whether the server supports the RDP feature we are possibly shimming.
923 */
924 onPacketTest: function (aPacket) {
925 throw new Error("Not yet implemented");
926 },
928 /**
929 * Takes a packet actually sent from the server and decides whether to replace
930 * it with a new packet, create an extra packet, or keep it.
931 */
932 translatePacket: function (aPacket, aReplacePacket, aExtraPacket, aKeepPacket) {
933 throw new Error("Not yet implemented");
934 }
935 };
937 /**
938 * Creates a tab client for the remote debugging protocol server. This client
939 * is a front to the tab actor created in the server side, hiding the protocol
940 * details in a traditional JavaScript API.
941 *
942 * @param aClient DebuggerClient
943 * The debugger client parent.
944 * @param aForm object
945 * The protocol form for this tab.
946 */
947 function TabClient(aClient, aForm) {
948 this.client = aClient;
949 this._actor = aForm.from;
950 this._threadActor = aForm.threadActor;
951 this.javascriptEnabled = aForm.javascriptEnabled;
952 this.cacheEnabled = aForm.cacheEnabled;
953 this.thread = null;
954 this.request = this.client.request;
955 this.traits = aForm.traits || {};
956 }
958 TabClient.prototype = {
959 get actor() { return this._actor },
960 get _transport() { return this.client._transport; },
962 /**
963 * Attach to a thread actor.
964 *
965 * @param object aOptions
966 * Configuration options.
967 * - useSourceMaps: whether to use source maps or not.
968 * @param function aOnResponse
969 * Called with the response packet and a ThreadClient
970 * (which will be undefined on error).
971 */
972 attachThread: function(aOptions={}, aOnResponse) {
973 if (this.thread) {
974 setTimeout(() => aOnResponse({}, this.thread), 0);
975 return;
976 }
978 let packet = {
979 to: this._threadActor,
980 type: "attach",
981 options: aOptions
982 };
983 this.request(packet, (aResponse) => {
984 if (!aResponse.error) {
985 this.thread = new ThreadClient(this, this._threadActor);
986 this.client._threadClients.set(this._threadActor, this.thread);
987 }
988 aOnResponse(aResponse, this.thread);
989 });
990 },
992 /**
993 * Detach the client from the tab actor.
994 *
995 * @param function aOnResponse
996 * Called with the response packet.
997 */
998 detach: DebuggerClient.requester({
999 type: "detach"
1000 }, {
1001 before: function (aPacket) {
1002 if (this.thread) {
1003 this.thread.detach();
1004 }
1005 return aPacket;
1006 },
1007 after: function (aResponse) {
1008 this.client._tabClients.delete(this.actor);
1009 return aResponse;
1010 },
1011 telemetry: "TABDETACH"
1012 }),
1014 /**
1015 * Reload the page in this tab.
1016 */
1017 reload: DebuggerClient.requester({
1018 type: "reload"
1019 }, {
1020 telemetry: "RELOAD"
1021 }),
1023 /**
1024 * Navigate to another URL.
1025 *
1026 * @param string url
1027 * The URL to navigate to.
1028 */
1029 navigateTo: DebuggerClient.requester({
1030 type: "navigateTo",
1031 url: args(0)
1032 }, {
1033 telemetry: "NAVIGATETO"
1034 }),
1036 /**
1037 * Reconfigure the tab actor.
1038 *
1039 * @param object aOptions
1040 * A dictionary object of the new options to use in the tab actor.
1041 * @param function aOnResponse
1042 * Called with the response packet.
1043 */
1044 reconfigure: DebuggerClient.requester({
1045 type: "reconfigure",
1046 options: args(0)
1047 }, {
1048 telemetry: "RECONFIGURETAB"
1049 }),
1050 };
1052 eventSource(TabClient.prototype);
1054 function AddonClient(aClient, aActor) {
1055 this._client = aClient;
1056 this._actor = aActor;
1057 this.request = this._client.request;
1058 }
1060 AddonClient.prototype = {
1061 get actor() { return this._actor; },
1062 get _transport() { return this._client._transport; },
1064 /**
1065 * Detach the client from the addon actor.
1066 *
1067 * @param function aOnResponse
1068 * Called with the response packet.
1069 */
1070 detach: DebuggerClient.requester({
1071 type: "detach"
1072 }, {
1073 after: function(aResponse) {
1074 if (this._client.activeAddon === this._client._addonClients[this.actor]) {
1075 this._client.activeAddon = null
1076 }
1077 delete this._client._addonClients[this.actor];
1078 return aResponse;
1079 },
1080 telemetry: "ADDONDETACH"
1081 })
1082 };
1084 /**
1085 * A RootClient object represents a root actor on the server. Each
1086 * DebuggerClient keeps a RootClient instance representing the root actor
1087 * for the initial connection; DebuggerClient's 'listTabs' and
1088 * 'listChildProcesses' methods forward to that root actor.
1089 *
1090 * @param aClient object
1091 * The client connection to which this actor belongs.
1092 * @param aGreeting string
1093 * The greeting packet from the root actor we're to represent.
1094 *
1095 * Properties of a RootClient instance:
1096 *
1097 * @property actor string
1098 * The name of this child's root actor.
1099 * @property applicationType string
1100 * The application type, as given in the root actor's greeting packet.
1101 * @property traits object
1102 * The traits object, as given in the root actor's greeting packet.
1103 */
1104 function RootClient(aClient, aGreeting) {
1105 this._client = aClient;
1106 this.actor = aGreeting.from;
1107 this.applicationType = aGreeting.applicationType;
1108 this.traits = aGreeting.traits;
1109 }
1111 RootClient.prototype = {
1112 constructor: RootClient,
1114 /**
1115 * List the open tabs.
1116 *
1117 * @param function aOnResponse
1118 * Called with the response packet.
1119 */
1120 listTabs: DebuggerClient.requester({ type: "listTabs" },
1121 { telemetry: "LISTTABS" }),
1123 /**
1124 * List the installed addons.
1125 *
1126 * @param function aOnResponse
1127 * Called with the response packet.
1128 */
1129 listAddons: DebuggerClient.requester({ type: "listAddons" },
1130 { telemetry: "LISTADDONS" }),
1132 /*
1133 * Methods constructed by DebuggerClient.requester require these forwards
1134 * on their 'this'.
1135 */
1136 get _transport() { return this._client._transport; },
1137 get request() { return this._client.request; }
1138 };
1140 /**
1141 * Creates a thread client for the remote debugging protocol server. This client
1142 * is a front to the thread actor created in the server side, hiding the
1143 * protocol details in a traditional JavaScript API.
1144 *
1145 * @param aClient DebuggerClient|TabClient
1146 * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
1147 * for chrome debuggers).
1148 * @param aActor string
1149 * The actor ID for this thread.
1150 */
1151 function ThreadClient(aClient, aActor) {
1152 this._parent = aClient;
1153 this.client = aClient instanceof DebuggerClient ? aClient : aClient.client;
1154 this._actor = aActor;
1155 this._frameCache = [];
1156 this._scriptCache = {};
1157 this._pauseGrips = {};
1158 this._threadGrips = {};
1159 this.request = this.client.request;
1160 }
1162 ThreadClient.prototype = {
1163 _state: "paused",
1164 get state() { return this._state; },
1165 get paused() { return this._state === "paused"; },
1167 _pauseOnExceptions: false,
1168 _ignoreCaughtExceptions: false,
1169 _pauseOnDOMEvents: null,
1171 _actor: null,
1172 get actor() { return this._actor; },
1174 get compat() { return this.client.compat; },
1175 get _transport() { return this.client._transport; },
1177 _assertPaused: function (aCommand) {
1178 if (!this.paused) {
1179 throw Error(aCommand + " command sent while not paused. Currently " + this._state);
1180 }
1181 },
1183 /**
1184 * Resume a paused thread. If the optional aLimit parameter is present, then
1185 * the thread will also pause when that limit is reached.
1186 *
1187 * @param [optional] object aLimit
1188 * An object with a type property set to the appropriate limit (next,
1189 * step, or finish) per the remote debugging protocol specification.
1190 * Use null to specify no limit.
1191 * @param function aOnResponse
1192 * Called with the response packet.
1193 */
1194 _doResume: DebuggerClient.requester({
1195 type: "resume",
1196 resumeLimit: args(0)
1197 }, {
1198 before: function (aPacket) {
1199 this._assertPaused("resume");
1201 // Put the client in a tentative "resuming" state so we can prevent
1202 // further requests that should only be sent in the paused state.
1203 this._state = "resuming";
1205 if (this._pauseOnExceptions) {
1206 aPacket.pauseOnExceptions = this._pauseOnExceptions;
1207 }
1208 if (this._ignoreCaughtExceptions) {
1209 aPacket.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
1210 }
1211 if (this._pauseOnDOMEvents) {
1212 aPacket.pauseOnDOMEvents = this._pauseOnDOMEvents;
1213 }
1214 return aPacket;
1215 },
1216 after: function (aResponse) {
1217 if (aResponse.error) {
1218 // There was an error resuming, back to paused state.
1219 this._state = "paused";
1220 }
1221 return aResponse;
1222 },
1223 telemetry: "RESUME"
1224 }),
1226 /**
1227 * Reconfigure the thread actor.
1228 *
1229 * @param object aOptions
1230 * A dictionary object of the new options to use in the thread actor.
1231 * @param function aOnResponse
1232 * Called with the response packet.
1233 */
1234 reconfigure: DebuggerClient.requester({
1235 type: "reconfigure",
1236 options: args(0)
1237 }, {
1238 telemetry: "RECONFIGURETHREAD"
1239 }),
1241 /**
1242 * Resume a paused thread.
1243 */
1244 resume: function (aOnResponse) {
1245 this._doResume(null, aOnResponse);
1246 },
1248 /**
1249 * Step over a function call.
1250 *
1251 * @param function aOnResponse
1252 * Called with the response packet.
1253 */
1254 stepOver: function (aOnResponse) {
1255 this._doResume({ type: "next" }, aOnResponse);
1256 },
1258 /**
1259 * Step into a function call.
1260 *
1261 * @param function aOnResponse
1262 * Called with the response packet.
1263 */
1264 stepIn: function (aOnResponse) {
1265 this._doResume({ type: "step" }, aOnResponse);
1266 },
1268 /**
1269 * Step out of a function call.
1270 *
1271 * @param function aOnResponse
1272 * Called with the response packet.
1273 */
1274 stepOut: function (aOnResponse) {
1275 this._doResume({ type: "finish" }, aOnResponse);
1276 },
1278 /**
1279 * Interrupt a running thread.
1280 *
1281 * @param function aOnResponse
1282 * Called with the response packet.
1283 */
1284 interrupt: DebuggerClient.requester({
1285 type: "interrupt"
1286 }, {
1287 telemetry: "INTERRUPT"
1288 }),
1290 /**
1291 * Enable or disable pausing when an exception is thrown.
1292 *
1293 * @param boolean aFlag
1294 * Enables pausing if true, disables otherwise.
1295 * @param function aOnResponse
1296 * Called with the response packet.
1297 */
1298 pauseOnExceptions: function (aPauseOnExceptions,
1299 aIgnoreCaughtExceptions,
1300 aOnResponse) {
1301 this._pauseOnExceptions = aPauseOnExceptions;
1302 this._ignoreCaughtExceptions = aIgnoreCaughtExceptions;
1304 // If the debuggee is paused, we have to send the flag via a reconfigure
1305 // request.
1306 if (this.paused) {
1307 this.reconfigure({
1308 pauseOnExceptions: aPauseOnExceptions,
1309 ignoreCaughtExceptions: aIgnoreCaughtExceptions
1310 }, aOnResponse);
1311 return;
1312 }
1313 // Otherwise send the flag using a standard resume request.
1314 this.interrupt(aResponse => {
1315 if (aResponse.error) {
1316 // Can't continue if pausing failed.
1317 aOnResponse(aResponse);
1318 return;
1319 }
1320 this.resume(aOnResponse);
1321 });
1322 },
1324 /**
1325 * Enable pausing when the specified DOM events are triggered. Disabling
1326 * pausing on an event can be realized by calling this method with the updated
1327 * array of events that doesn't contain it.
1328 *
1329 * @param array|string events
1330 * An array of strings, representing the DOM event types to pause on,
1331 * or "*" to pause on all DOM events. Pass an empty array to
1332 * completely disable pausing on DOM events.
1333 * @param function onResponse
1334 * Called with the response packet in a future turn of the event loop.
1335 */
1336 pauseOnDOMEvents: function (events, onResponse) {
1337 this._pauseOnDOMEvents = events;
1338 // If the debuggee is paused, the value of the array will be communicated in
1339 // the next resumption. Otherwise we have to force a pause in order to send
1340 // the array.
1341 if (this.paused) {
1342 setTimeout(() => onResponse({}), 0);
1343 return;
1344 }
1345 this.interrupt(response => {
1346 // Can't continue if pausing failed.
1347 if (response.error) {
1348 onResponse(response);
1349 return;
1350 }
1351 this.resume(onResponse);
1352 });
1353 },
1355 /**
1356 * Send a clientEvaluate packet to the debuggee. Response
1357 * will be a resume packet.
1358 *
1359 * @param string aFrame
1360 * The actor ID of the frame where the evaluation should take place.
1361 * @param string aExpression
1362 * The expression that will be evaluated in the scope of the frame
1363 * above.
1364 * @param function aOnResponse
1365 * Called with the response packet.
1366 */
1367 eval: DebuggerClient.requester({
1368 type: "clientEvaluate",
1369 frame: args(0),
1370 expression: args(1)
1371 }, {
1372 before: function (aPacket) {
1373 this._assertPaused("eval");
1374 // Put the client in a tentative "resuming" state so we can prevent
1375 // further requests that should only be sent in the paused state.
1376 this._state = "resuming";
1377 return aPacket;
1378 },
1379 after: function (aResponse) {
1380 if (aResponse.error) {
1381 // There was an error resuming, back to paused state.
1382 this._state = "paused";
1383 }
1384 return aResponse;
1385 },
1386 telemetry: "CLIENTEVALUATE"
1387 }),
1389 /**
1390 * Detach from the thread actor.
1391 *
1392 * @param function aOnResponse
1393 * Called with the response packet.
1394 */
1395 detach: DebuggerClient.requester({
1396 type: "detach"
1397 }, {
1398 after: function (aResponse) {
1399 this.client._threadClients.delete(this.actor);
1400 this._parent.thread = null;
1401 return aResponse;
1402 },
1403 telemetry: "THREADDETACH"
1404 }),
1406 /**
1407 * Request to set a breakpoint in the specified location.
1408 *
1409 * @param object aLocation
1410 * The source location object where the breakpoint will be set.
1411 * @param function aOnResponse
1412 * Called with the thread's response.
1413 */
1414 setBreakpoint: function ({ url, line, column, condition }, aOnResponse) {
1415 // A helper function that sets the breakpoint.
1416 let doSetBreakpoint = function (aCallback) {
1417 const location = {
1418 url: url,
1419 line: line,
1420 column: column
1421 };
1423 let packet = {
1424 to: this._actor,
1425 type: "setBreakpoint",
1426 location: location,
1427 condition: condition
1428 };
1429 this.client.request(packet, function (aResponse) {
1430 // Ignoring errors, since the user may be setting a breakpoint in a
1431 // dead script that will reappear on a page reload.
1432 if (aOnResponse) {
1433 let root = this.client.mainRoot;
1434 let bpClient = new BreakpointClient(
1435 this.client,
1436 aResponse.actor,
1437 location,
1438 root.traits.conditionalBreakpoints ? condition : undefined
1439 );
1440 aOnResponse(aResponse, bpClient);
1441 }
1442 if (aCallback) {
1443 aCallback();
1444 }
1445 }.bind(this));
1446 }.bind(this);
1448 // If the debuggee is paused, just set the breakpoint.
1449 if (this.paused) {
1450 doSetBreakpoint();
1451 return;
1452 }
1453 // Otherwise, force a pause in order to set the breakpoint.
1454 this.interrupt(function (aResponse) {
1455 if (aResponse.error) {
1456 // Can't set the breakpoint if pausing failed.
1457 aOnResponse(aResponse);
1458 return;
1459 }
1460 doSetBreakpoint(this.resume.bind(this));
1461 }.bind(this));
1462 },
1464 /**
1465 * Release multiple thread-lifetime object actors. If any pause-lifetime
1466 * actors are included in the request, a |notReleasable| error will return,
1467 * but all the thread-lifetime ones will have been released.
1468 *
1469 * @param array actors
1470 * An array with actor IDs to release.
1471 */
1472 releaseMany: DebuggerClient.requester({
1473 type: "releaseMany",
1474 actors: args(0),
1475 }, {
1476 telemetry: "RELEASEMANY"
1477 }),
1479 /**
1480 * Promote multiple pause-lifetime object actors to thread-lifetime ones.
1481 *
1482 * @param array actors
1483 * An array with actor IDs to promote.
1484 */
1485 threadGrips: DebuggerClient.requester({
1486 type: "threadGrips",
1487 actors: args(0)
1488 }, {
1489 telemetry: "THREADGRIPS"
1490 }),
1492 /**
1493 * Return the event listeners defined on the page.
1494 *
1495 * @param aOnResponse Function
1496 * Called with the thread's response.
1497 */
1498 eventListeners: DebuggerClient.requester({
1499 type: "eventListeners"
1500 }, {
1501 telemetry: "EVENTLISTENERS"
1502 }),
1504 /**
1505 * Request the loaded sources for the current thread.
1506 *
1507 * @param aOnResponse Function
1508 * Called with the thread's response.
1509 */
1510 getSources: DebuggerClient.requester({
1511 type: "sources"
1512 }, {
1513 telemetry: "SOURCES"
1514 }),
1516 _doInterrupted: function (aAction, aError) {
1517 if (this.paused) {
1518 aAction();
1519 return;
1520 }
1521 this.interrupt(function (aResponse) {
1522 if (aResponse) {
1523 aError(aResponse);
1524 return;
1525 }
1526 aAction();
1527 this.resume();
1528 }.bind(this));
1529 },
1531 /**
1532 * Clear the thread's source script cache. A scriptscleared event
1533 * will be sent.
1534 */
1535 _clearScripts: function () {
1536 if (Object.keys(this._scriptCache).length > 0) {
1537 this._scriptCache = {}
1538 this.notify("scriptscleared");
1539 }
1540 },
1542 /**
1543 * Request frames from the callstack for the current thread.
1544 *
1545 * @param aStart integer
1546 * The number of the youngest stack frame to return (the youngest
1547 * frame is 0).
1548 * @param aCount integer
1549 * The maximum number of frames to return, or null to return all
1550 * frames.
1551 * @param aOnResponse function
1552 * Called with the thread's response.
1553 */
1554 getFrames: DebuggerClient.requester({
1555 type: "frames",
1556 start: args(0),
1557 count: args(1)
1558 }, {
1559 telemetry: "FRAMES"
1560 }),
1562 /**
1563 * An array of cached frames. Clients can observe the framesadded and
1564 * framescleared event to keep up to date on changes to this cache,
1565 * and can fill it using the fillFrames method.
1566 */
1567 get cachedFrames() { return this._frameCache; },
1569 /**
1570 * true if there are more stack frames available on the server.
1571 */
1572 get moreFrames() {
1573 return this.paused && (!this._frameCache || this._frameCache.length == 0
1574 || !this._frameCache[this._frameCache.length - 1].oldest);
1575 },
1577 /**
1578 * Ensure that at least aTotal stack frames have been loaded in the
1579 * ThreadClient's stack frame cache. A framesadded event will be
1580 * sent when the stack frame cache is updated.
1581 *
1582 * @param aTotal number
1583 * The minimum number of stack frames to be included.
1584 * @param aCallback function
1585 * Optional callback function called when frames have been loaded
1586 * @returns true if a framesadded notification should be expected.
1587 */
1588 fillFrames: function (aTotal, aCallback=noop) {
1589 this._assertPaused("fillFrames");
1590 if (this._frameCache.length >= aTotal) {
1591 return false;
1592 }
1594 let numFrames = this._frameCache.length;
1596 this.getFrames(numFrames, aTotal - numFrames, (aResponse) => {
1597 if (aResponse.error) {
1598 aCallback(aResponse);
1599 return;
1600 }
1602 for each (let frame in aResponse.frames) {
1603 this._frameCache[frame.depth] = frame;
1604 }
1606 // If we got as many frames as we asked for, there might be more
1607 // frames available.
1608 this.notify("framesadded");
1610 aCallback(aResponse);
1611 });
1613 return true;
1614 },
1616 /**
1617 * Clear the thread's stack frame cache. A framescleared event
1618 * will be sent.
1619 */
1620 _clearFrames: function () {
1621 if (this._frameCache.length > 0) {
1622 this._frameCache = [];
1623 this.notify("framescleared");
1624 }
1625 },
1627 /**
1628 * Return a ObjectClient object for the given object grip.
1629 *
1630 * @param aGrip object
1631 * A pause-lifetime object grip returned by the protocol.
1632 */
1633 pauseGrip: function (aGrip) {
1634 if (aGrip.actor in this._pauseGrips) {
1635 return this._pauseGrips[aGrip.actor];
1636 }
1638 let client = new ObjectClient(this.client, aGrip);
1639 this._pauseGrips[aGrip.actor] = client;
1640 return client;
1641 },
1643 /**
1644 * Get or create a long string client, checking the grip client cache if it
1645 * already exists.
1646 *
1647 * @param aGrip Object
1648 * The long string grip returned by the protocol.
1649 * @param aGripCacheName String
1650 * The property name of the grip client cache to check for existing
1651 * clients in.
1652 */
1653 _longString: function (aGrip, aGripCacheName) {
1654 if (aGrip.actor in this[aGripCacheName]) {
1655 return this[aGripCacheName][aGrip.actor];
1656 }
1658 let client = new LongStringClient(this.client, aGrip);
1659 this[aGripCacheName][aGrip.actor] = client;
1660 return client;
1661 },
1663 /**
1664 * Return an instance of LongStringClient for the given long string grip that
1665 * is scoped to the current pause.
1666 *
1667 * @param aGrip Object
1668 * The long string grip returned by the protocol.
1669 */
1670 pauseLongString: function (aGrip) {
1671 return this._longString(aGrip, "_pauseGrips");
1672 },
1674 /**
1675 * Return an instance of LongStringClient for the given long string grip that
1676 * is scoped to the thread lifetime.
1677 *
1678 * @param aGrip Object
1679 * The long string grip returned by the protocol.
1680 */
1681 threadLongString: function (aGrip) {
1682 return this._longString(aGrip, "_threadGrips");
1683 },
1685 /**
1686 * Clear and invalidate all the grip clients from the given cache.
1687 *
1688 * @param aGripCacheName
1689 * The property name of the grip cache we want to clear.
1690 */
1691 _clearObjectClients: function (aGripCacheName) {
1692 for each (let grip in this[aGripCacheName]) {
1693 grip.valid = false;
1694 }
1695 this[aGripCacheName] = {};
1696 },
1698 /**
1699 * Invalidate pause-lifetime grip clients and clear the list of current grip
1700 * clients.
1701 */
1702 _clearPauseGrips: function () {
1703 this._clearObjectClients("_pauseGrips");
1704 },
1706 /**
1707 * Invalidate thread-lifetime grip clients and clear the list of current grip
1708 * clients.
1709 */
1710 _clearThreadGrips: function () {
1711 this._clearObjectClients("_threadGrips");
1712 },
1714 /**
1715 * Handle thread state change by doing necessary cleanup and notifying all
1716 * registered listeners.
1717 */
1718 _onThreadState: function (aPacket) {
1719 this._state = ThreadStateTypes[aPacket.type];
1720 this._clearFrames();
1721 this._clearPauseGrips();
1722 aPacket.type === ThreadStateTypes.detached && this._clearThreadGrips();
1723 this.client._eventsEnabled && this.notify(aPacket.type, aPacket);
1724 },
1726 /**
1727 * Return an EnvironmentClient instance for the given environment actor form.
1728 */
1729 environment: function (aForm) {
1730 return new EnvironmentClient(this.client, aForm);
1731 },
1733 /**
1734 * Return an instance of SourceClient for the given source actor form.
1735 */
1736 source: function (aForm) {
1737 if (aForm.actor in this._threadGrips) {
1738 return this._threadGrips[aForm.actor];
1739 }
1741 return this._threadGrips[aForm.actor] = new SourceClient(this, aForm);
1742 },
1744 /**
1745 * Request the prototype and own properties of mutlipleObjects.
1746 *
1747 * @param aOnResponse function
1748 * Called with the request's response.
1749 * @param actors [string]
1750 * List of actor ID of the queried objects.
1751 */
1752 getPrototypesAndProperties: DebuggerClient.requester({
1753 type: "prototypesAndProperties",
1754 actors: args(0)
1755 }, {
1756 telemetry: "PROTOTYPESANDPROPERTIES"
1757 })
1758 };
1760 eventSource(ThreadClient.prototype);
1762 /**
1763 * Creates a tracing profiler client for the remote debugging protocol
1764 * server. This client is a front to the trace actor created on the
1765 * server side, hiding the protocol details in a traditional
1766 * JavaScript API.
1767 *
1768 * @param aClient DebuggerClient
1769 * The debugger client parent.
1770 * @param aActor string
1771 * The actor ID for this thread.
1772 */
1773 function TraceClient(aClient, aActor) {
1774 this._client = aClient;
1775 this._actor = aActor;
1776 this._activeTraces = new Set();
1777 this._waitingPackets = new Map();
1778 this._expectedPacket = 0;
1779 this.request = this._client.request;
1780 }
1782 TraceClient.prototype = {
1783 get actor() { return this._actor; },
1784 get tracing() { return this._activeTraces.size > 0; },
1786 get _transport() { return this._client._transport; },
1788 /**
1789 * Detach from the trace actor.
1790 */
1791 detach: DebuggerClient.requester({
1792 type: "detach"
1793 }, {
1794 after: function (aResponse) {
1795 this._client._tracerClients.delete(this.actor);
1796 return aResponse;
1797 },
1798 telemetry: "TRACERDETACH"
1799 }),
1801 /**
1802 * Start a new trace.
1803 *
1804 * @param aTrace [string]
1805 * An array of trace types to be recorded by the new trace.
1806 *
1807 * @param aName string
1808 * The name of the new trace.
1809 *
1810 * @param aOnResponse function
1811 * Called with the request's response.
1812 */
1813 startTrace: DebuggerClient.requester({
1814 type: "startTrace",
1815 name: args(1),
1816 trace: args(0)
1817 }, {
1818 after: function (aResponse) {
1819 if (aResponse.error) {
1820 return aResponse;
1821 }
1823 if (!this.tracing) {
1824 this._waitingPackets.clear();
1825 this._expectedPacket = 0;
1826 }
1827 this._activeTraces.add(aResponse.name);
1829 return aResponse;
1830 },
1831 telemetry: "STARTTRACE"
1832 }),
1834 /**
1835 * End a trace. If a name is provided, stop the named
1836 * trace. Otherwise, stop the most recently started trace.
1837 *
1838 * @param aName string
1839 * The name of the trace to stop.
1840 *
1841 * @param aOnResponse function
1842 * Called with the request's response.
1843 */
1844 stopTrace: DebuggerClient.requester({
1845 type: "stopTrace",
1846 name: args(0)
1847 }, {
1848 after: function (aResponse) {
1849 if (aResponse.error) {
1850 return aResponse;
1851 }
1853 this._activeTraces.delete(aResponse.name);
1855 return aResponse;
1856 },
1857 telemetry: "STOPTRACE"
1858 })
1859 };
1861 /**
1862 * Grip clients are used to retrieve information about the relevant object.
1863 *
1864 * @param aClient DebuggerClient
1865 * The debugger client parent.
1866 * @param aGrip object
1867 * A pause-lifetime object grip returned by the protocol.
1868 */
1869 function ObjectClient(aClient, aGrip)
1870 {
1871 this._grip = aGrip;
1872 this._client = aClient;
1873 this.request = this._client.request;
1874 }
1876 ObjectClient.prototype = {
1877 get actor() { return this._grip.actor },
1878 get _transport() { return this._client._transport; },
1880 valid: true,
1882 get isFrozen() this._grip.frozen,
1883 get isSealed() this._grip.sealed,
1884 get isExtensible() this._grip.extensible,
1886 getDefinitionSite: DebuggerClient.requester({
1887 type: "definitionSite"
1888 }, {
1889 before: function (aPacket) {
1890 if (this._grip.class != "Function") {
1891 throw new Error("getDefinitionSite is only valid for function grips.");
1892 }
1893 return aPacket;
1894 }
1895 }),
1897 /**
1898 * Request the names of a function's formal parameters.
1899 *
1900 * @param aOnResponse function
1901 * Called with an object of the form:
1902 * { parameterNames:[<parameterName>, ...] }
1903 * where each <parameterName> is the name of a parameter.
1904 */
1905 getParameterNames: DebuggerClient.requester({
1906 type: "parameterNames"
1907 }, {
1908 before: function (aPacket) {
1909 if (this._grip["class"] !== "Function") {
1910 throw new Error("getParameterNames is only valid for function grips.");
1911 }
1912 return aPacket;
1913 },
1914 telemetry: "PARAMETERNAMES"
1915 }),
1917 /**
1918 * Request the names of the properties defined on the object and not its
1919 * prototype.
1920 *
1921 * @param aOnResponse function Called with the request's response.
1922 */
1923 getOwnPropertyNames: DebuggerClient.requester({
1924 type: "ownPropertyNames"
1925 }, {
1926 telemetry: "OWNPROPERTYNAMES"
1927 }),
1929 /**
1930 * Request the prototype and own properties of the object.
1931 *
1932 * @param aOnResponse function Called with the request's response.
1933 */
1934 getPrototypeAndProperties: DebuggerClient.requester({
1935 type: "prototypeAndProperties"
1936 }, {
1937 telemetry: "PROTOTYPEANDPROPERTIES"
1938 }),
1940 /**
1941 * Request the property descriptor of the object's specified property.
1942 *
1943 * @param aName string The name of the requested property.
1944 * @param aOnResponse function Called with the request's response.
1945 */
1946 getProperty: DebuggerClient.requester({
1947 type: "property",
1948 name: args(0)
1949 }, {
1950 telemetry: "PROPERTY"
1951 }),
1953 /**
1954 * Request the prototype of the object.
1955 *
1956 * @param aOnResponse function Called with the request's response.
1957 */
1958 getPrototype: DebuggerClient.requester({
1959 type: "prototype"
1960 }, {
1961 telemetry: "PROTOTYPE"
1962 }),
1964 /**
1965 * Request the display string of the object.
1966 *
1967 * @param aOnResponse function Called with the request's response.
1968 */
1969 getDisplayString: DebuggerClient.requester({
1970 type: "displayString"
1971 }, {
1972 telemetry: "DISPLAYSTRING"
1973 }),
1975 /**
1976 * Request the scope of the object.
1977 *
1978 * @param aOnResponse function Called with the request's response.
1979 */
1980 getScope: DebuggerClient.requester({
1981 type: "scope"
1982 }, {
1983 before: function (aPacket) {
1984 if (this._grip.class !== "Function") {
1985 throw new Error("scope is only valid for function grips.");
1986 }
1987 return aPacket;
1988 },
1989 telemetry: "SCOPE"
1990 })
1991 };
1993 /**
1994 * A LongStringClient provides a way to access "very long" strings from the
1995 * debugger server.
1996 *
1997 * @param aClient DebuggerClient
1998 * The debugger client parent.
1999 * @param aGrip Object
2000 * A pause-lifetime long string grip returned by the protocol.
2001 */
2002 function LongStringClient(aClient, aGrip) {
2003 this._grip = aGrip;
2004 this._client = aClient;
2005 this.request = this._client.request;
2006 }
2008 LongStringClient.prototype = {
2009 get actor() { return this._grip.actor; },
2010 get length() { return this._grip.length; },
2011 get initial() { return this._grip.initial; },
2012 get _transport() { return this._client._transport; },
2014 valid: true,
2016 /**
2017 * Get the substring of this LongString from aStart to aEnd.
2018 *
2019 * @param aStart Number
2020 * The starting index.
2021 * @param aEnd Number
2022 * The ending index.
2023 * @param aCallback Function
2024 * The function called when we receive the substring.
2025 */
2026 substring: DebuggerClient.requester({
2027 type: "substring",
2028 start: args(0),
2029 end: args(1)
2030 }, {
2031 telemetry: "SUBSTRING"
2032 }),
2033 };
2035 /**
2036 * A SourceClient provides a way to access the source text of a script.
2037 *
2038 * @param aClient ThreadClient
2039 * The thread client parent.
2040 * @param aForm Object
2041 * The form sent across the remote debugging protocol.
2042 */
2043 function SourceClient(aClient, aForm) {
2044 this._form = aForm;
2045 this._isBlackBoxed = aForm.isBlackBoxed;
2046 this._isPrettyPrinted = aForm.isPrettyPrinted;
2047 this._activeThread = aClient;
2048 this._client = aClient.client;
2049 }
2051 SourceClient.prototype = {
2052 get _transport() this._client._transport,
2053 get isBlackBoxed() this._isBlackBoxed,
2054 get isPrettyPrinted() this._isPrettyPrinted,
2055 get actor() this._form.actor,
2056 get request() this._client.request,
2057 get url() this._form.url,
2059 /**
2060 * Black box this SourceClient's source.
2061 *
2062 * @param aCallback Function
2063 * The callback function called when we receive the response from the server.
2064 */
2065 blackBox: DebuggerClient.requester({
2066 type: "blackbox"
2067 }, {
2068 telemetry: "BLACKBOX",
2069 after: function (aResponse) {
2070 if (!aResponse.error) {
2071 this._isBlackBoxed = true;
2072 if (this._activeThread) {
2073 this._activeThread.notify("blackboxchange", this);
2074 }
2075 }
2076 return aResponse;
2077 }
2078 }),
2080 /**
2081 * Un-black box this SourceClient's source.
2082 *
2083 * @param aCallback Function
2084 * The callback function called when we receive the response from the server.
2085 */
2086 unblackBox: DebuggerClient.requester({
2087 type: "unblackbox"
2088 }, {
2089 telemetry: "UNBLACKBOX",
2090 after: function (aResponse) {
2091 if (!aResponse.error) {
2092 this._isBlackBoxed = false;
2093 if (this._activeThread) {
2094 this._activeThread.notify("blackboxchange", this);
2095 }
2096 }
2097 return aResponse;
2098 }
2099 }),
2101 /**
2102 * Get a long string grip for this SourceClient's source.
2103 */
2104 source: function (aCallback) {
2105 let packet = {
2106 to: this._form.actor,
2107 type: "source"
2108 };
2109 this._client.request(packet, aResponse => {
2110 this._onSourceResponse(aResponse, aCallback)
2111 });
2112 },
2114 /**
2115 * Pretty print this source's text.
2116 */
2117 prettyPrint: function (aIndent, aCallback) {
2118 const packet = {
2119 to: this._form.actor,
2120 type: "prettyPrint",
2121 indent: aIndent
2122 };
2123 this._client.request(packet, aResponse => {
2124 if (!aResponse.error) {
2125 this._isPrettyPrinted = true;
2126 this._activeThread._clearFrames();
2127 this._activeThread.notify("prettyprintchange", this);
2128 }
2129 this._onSourceResponse(aResponse, aCallback);
2130 });
2131 },
2133 /**
2134 * Stop pretty printing this source's text.
2135 */
2136 disablePrettyPrint: function (aCallback) {
2137 const packet = {
2138 to: this._form.actor,
2139 type: "disablePrettyPrint"
2140 };
2141 this._client.request(packet, aResponse => {
2142 if (!aResponse.error) {
2143 this._isPrettyPrinted = false;
2144 this._activeThread._clearFrames();
2145 this._activeThread.notify("prettyprintchange", this);
2146 }
2147 this._onSourceResponse(aResponse, aCallback);
2148 });
2149 },
2151 _onSourceResponse: function (aResponse, aCallback) {
2152 if (aResponse.error) {
2153 aCallback(aResponse);
2154 return;
2155 }
2157 if (typeof aResponse.source === "string") {
2158 aCallback(aResponse);
2159 return;
2160 }
2162 let { contentType, source } = aResponse;
2163 let longString = this._activeThread.threadLongString(source);
2164 longString.substring(0, longString.length, function (aResponse) {
2165 if (aResponse.error) {
2166 aCallback(aResponse);
2167 return;
2168 }
2170 aCallback({
2171 source: aResponse.substring,
2172 contentType: contentType
2173 });
2174 });
2175 }
2176 };
2178 /**
2179 * Breakpoint clients are used to remove breakpoints that are no longer used.
2180 *
2181 * @param aClient DebuggerClient
2182 * The debugger client parent.
2183 * @param aActor string
2184 * The actor ID for this breakpoint.
2185 * @param aLocation object
2186 * The location of the breakpoint. This is an object with two properties:
2187 * url and line.
2188 * @param aCondition string
2189 * The conditional expression of the breakpoint
2190 */
2191 function BreakpointClient(aClient, aActor, aLocation, aCondition) {
2192 this._client = aClient;
2193 this._actor = aActor;
2194 this.location = aLocation;
2195 this.request = this._client.request;
2197 // The condition property should only exist if it's a truthy value
2198 if (aCondition) {
2199 this.condition = aCondition;
2200 }
2201 }
2203 BreakpointClient.prototype = {
2205 _actor: null,
2206 get actor() { return this._actor; },
2207 get _transport() { return this._client._transport; },
2209 /**
2210 * Remove the breakpoint from the server.
2211 */
2212 remove: DebuggerClient.requester({
2213 type: "delete"
2214 }, {
2215 telemetry: "DELETE"
2216 }),
2218 /**
2219 * Determines if this breakpoint has a condition
2220 */
2221 hasCondition: function() {
2222 let root = this._client.mainRoot;
2223 // XXX bug 990137: We will remove support for client-side handling of
2224 // conditional breakpoints
2225 if (root.traits.conditionalBreakpoints) {
2226 return "condition" in this;
2227 } else {
2228 return "conditionalExpression" in this;
2229 }
2230 },
2232 /**
2233 * Get the condition of this breakpoint. Currently we have to
2234 * support locally emulated conditional breakpoints until the
2235 * debugger servers are updated (see bug 990137). We used a
2236 * different property when moving it server-side to ensure that we
2237 * are testing the right code.
2238 */
2239 getCondition: function() {
2240 let root = this._client.mainRoot;
2241 if (root.traits.conditionalBreakpoints) {
2242 return this.condition;
2243 } else {
2244 return this.conditionalExpression;
2245 }
2246 },
2248 /**
2249 * Set the condition of this breakpoint
2250 */
2251 setCondition: function(gThreadClient, aCondition) {
2252 let root = this._client.mainRoot;
2253 let deferred = promise.defer();
2255 if (root.traits.conditionalBreakpoints) {
2256 let info = {
2257 url: this.location.url,
2258 line: this.location.line,
2259 column: this.location.column,
2260 condition: aCondition
2261 };
2263 // Remove the current breakpoint and add a new one with the
2264 // condition.
2265 this.remove(aResponse => {
2266 if (aResponse && aResponse.error) {
2267 deferred.reject(aResponse);
2268 return;
2269 }
2271 gThreadClient.setBreakpoint(info, (aResponse, aNewBreakpoint) => {
2272 if (aResponse && aResponse.error) {
2273 deferred.reject(aResponse);
2274 } else {
2275 deferred.resolve(aNewBreakpoint);
2276 }
2277 });
2278 });
2279 } else {
2280 // The property shouldn't even exist if the condition is blank
2281 if(aCondition === "") {
2282 delete this.conditionalExpression;
2283 }
2284 else {
2285 this.conditionalExpression = aCondition;
2286 }
2287 deferred.resolve(this);
2288 }
2290 return deferred.promise;
2291 }
2292 };
2294 eventSource(BreakpointClient.prototype);
2296 /**
2297 * Environment clients are used to manipulate the lexical environment actors.
2298 *
2299 * @param aClient DebuggerClient
2300 * The debugger client parent.
2301 * @param aForm Object
2302 * The form sent across the remote debugging protocol.
2303 */
2304 function EnvironmentClient(aClient, aForm) {
2305 this._client = aClient;
2306 this._form = aForm;
2307 this.request = this._client.request;
2308 }
2310 EnvironmentClient.prototype = {
2312 get actor() this._form.actor,
2313 get _transport() { return this._client._transport; },
2315 /**
2316 * Fetches the bindings introduced by this lexical environment.
2317 */
2318 getBindings: DebuggerClient.requester({
2319 type: "bindings"
2320 }, {
2321 telemetry: "BINDINGS"
2322 }),
2324 /**
2325 * Changes the value of the identifier whose name is name (a string) to that
2326 * represented by value (a grip).
2327 */
2328 assign: DebuggerClient.requester({
2329 type: "assign",
2330 name: args(0),
2331 value: args(1)
2332 }, {
2333 telemetry: "ASSIGN"
2334 })
2335 };
2337 eventSource(EnvironmentClient.prototype);
2339 /**
2340 * Connects to a debugger server socket and returns a DebuggerTransport.
2341 *
2342 * @param aHost string
2343 * The host name or IP address of the debugger server.
2344 * @param aPort number
2345 * The port number of the debugger server.
2346 */
2347 this.debuggerSocketConnect = function (aHost, aPort)
2348 {
2349 let s = socketTransportService.createTransport(null, 0, aHost, aPort, null);
2350 // By default the CONNECT socket timeout is very long, 65535 seconds,
2351 // so that if we race to be in CONNECT state while the server socket is still
2352 // initializing, the connection is stuck in connecting state for 18.20 hours!
2353 s.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 2);
2355 // openOutputStream may throw NS_ERROR_NOT_INITIALIZED if we hit some race
2356 // where the nsISocketTransport gets shutdown in between its instantiation and
2357 // the call to this method.
2358 let transport;
2359 try {
2360 transport = new DebuggerTransport(s.openInputStream(0, 0, 0),
2361 s.openOutputStream(0, 0, 0));
2362 } catch(e) {
2363 DevToolsUtils.reportException("debuggerSocketConnect", e);
2364 throw e;
2365 }
2366 return transport;
2367 }
2369 /**
2370 * Takes a pair of items and returns them as an array.
2371 */
2372 function pair(aItemOne, aItemTwo) {
2373 return [aItemOne, aItemTwo];
2374 }
2375 function noop() {}