main.js 92 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. "use strict";
  6. const { Ci, Cu } = require("chrome");
  7. const Services = require("Services");
  8. const DevToolsUtils = require("devtools/shared/DevToolsUtils");
  9. const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
  10. const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
  11. loader.lazyRequireGetter(this, "events", "sdk/event/core");
  12. loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
  13. loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
  14. loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
  15. const noop = () => {};
  16. /**
  17. * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
  18. *
  19. * Add simple event notification to a prototype object. Any object that has
  20. * some use for event notifications or the observer pattern in general can be
  21. * augmented with the necessary facilities by passing its prototype to this
  22. * function.
  23. *
  24. * @param aProto object
  25. * The prototype object that will be modified.
  26. */
  27. function eventSource(aProto) {
  28. /**
  29. * Add a listener to the event source for a given event.
  30. *
  31. * @param aName string
  32. * The event to listen for.
  33. * @param aListener function
  34. * Called when the event is fired. If the same listener
  35. * is added more than once, it will be called once per
  36. * addListener call.
  37. */
  38. aProto.addListener = function (aName, aListener) {
  39. if (typeof aListener != "function") {
  40. throw TypeError("Listeners must be functions.");
  41. }
  42. if (!this._listeners) {
  43. this._listeners = {};
  44. }
  45. this._getListeners(aName).push(aListener);
  46. };
  47. /**
  48. * Add a listener to the event source for a given event. The
  49. * listener will be removed after it is called for the first time.
  50. *
  51. * @param aName string
  52. * The event to listen for.
  53. * @param aListener function
  54. * Called when the event is fired.
  55. */
  56. aProto.addOneTimeListener = function (aName, aListener) {
  57. let l = (...args) => {
  58. this.removeListener(aName, l);
  59. aListener.apply(null, args);
  60. };
  61. this.addListener(aName, l);
  62. };
  63. /**
  64. * Remove a listener from the event source previously added with
  65. * addListener().
  66. *
  67. * @param aName string
  68. * The event name used during addListener to add the listener.
  69. * @param aListener function
  70. * The callback to remove. If addListener was called multiple
  71. * times, all instances will be removed.
  72. */
  73. aProto.removeListener = function (aName, aListener) {
  74. if (!this._listeners || (aListener && !this._listeners[aName])) {
  75. return;
  76. }
  77. if (!aListener) {
  78. this._listeners[aName] = [];
  79. }
  80. else {
  81. this._listeners[aName] =
  82. this._listeners[aName].filter(function (l) { return l != aListener; });
  83. }
  84. };
  85. /**
  86. * Returns the listeners for the specified event name. If none are defined it
  87. * initializes an empty list and returns that.
  88. *
  89. * @param aName string
  90. * The event name.
  91. */
  92. aProto._getListeners = function (aName) {
  93. if (aName in this._listeners) {
  94. return this._listeners[aName];
  95. }
  96. this._listeners[aName] = [];
  97. return this._listeners[aName];
  98. };
  99. /**
  100. * Notify listeners of an event.
  101. *
  102. * @param aName string
  103. * The event to fire.
  104. * @param arguments
  105. * All arguments will be passed along to the listeners,
  106. * including the name argument.
  107. */
  108. aProto.emit = function () {
  109. if (!this._listeners) {
  110. return;
  111. }
  112. let name = arguments[0];
  113. let listeners = this._getListeners(name).slice(0);
  114. for (let listener of listeners) {
  115. try {
  116. listener.apply(null, arguments);
  117. } catch (e) {
  118. // Prevent a bad listener from interfering with the others.
  119. DevToolsUtils.reportException("notify event '" + name + "'", e);
  120. }
  121. }
  122. };
  123. }
  124. /**
  125. * Set of protocol messages that affect thread state, and the
  126. * state the actor is in after each message.
  127. */
  128. const ThreadStateTypes = {
  129. "paused": "paused",
  130. "resumed": "attached",
  131. "detached": "detached",
  132. "running": "attached"
  133. };
  134. /**
  135. * Set of protocol messages that are sent by the server without a prior request
  136. * by the client.
  137. */
  138. const UnsolicitedNotifications = {
  139. "consoleAPICall": "consoleAPICall",
  140. "eventNotification": "eventNotification",
  141. "fileActivity": "fileActivity",
  142. "lastPrivateContextExited": "lastPrivateContextExited",
  143. "logMessage": "logMessage",
  144. "networkEvent": "networkEvent",
  145. "networkEventUpdate": "networkEventUpdate",
  146. "newGlobal": "newGlobal",
  147. "newScript": "newScript",
  148. "tabDetached": "tabDetached",
  149. "tabListChanged": "tabListChanged",
  150. "reflowActivity": "reflowActivity",
  151. "addonListChanged": "addonListChanged",
  152. "workerListChanged": "workerListChanged",
  153. "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
  154. "tabNavigated": "tabNavigated",
  155. "frameUpdate": "frameUpdate",
  156. "pageError": "pageError",
  157. "documentLoad": "documentLoad",
  158. "enteredFrame": "enteredFrame",
  159. "exitedFrame": "exitedFrame",
  160. "appOpen": "appOpen",
  161. "appClose": "appClose",
  162. "appInstall": "appInstall",
  163. "appUninstall": "appUninstall",
  164. "evaluationResult": "evaluationResult",
  165. "newSource": "newSource",
  166. "updatedSource": "updatedSource",
  167. };
  168. /**
  169. * Set of pause types that are sent by the server and not as an immediate
  170. * response to a client request.
  171. */
  172. const UnsolicitedPauses = {
  173. "resumeLimit": "resumeLimit",
  174. "debuggerStatement": "debuggerStatement",
  175. "breakpoint": "breakpoint",
  176. "DOMEvent": "DOMEvent",
  177. "watchpoint": "watchpoint",
  178. "exception": "exception"
  179. };
  180. /**
  181. * Creates a client for the remote debugging protocol server. This client
  182. * provides the means to communicate with the server and exchange the messages
  183. * required by the protocol in a traditional JavaScript API.
  184. */
  185. const DebuggerClient = exports.DebuggerClient = function (aTransport)
  186. {
  187. this._transport = aTransport;
  188. this._transport.hooks = this;
  189. // Map actor ID to client instance for each actor type.
  190. this._clients = new Map();
  191. this._pendingRequests = new Map();
  192. this._activeRequests = new Map();
  193. this._eventsEnabled = true;
  194. this.traits = {};
  195. this.request = this.request.bind(this);
  196. this.localTransport = this._transport.onOutputStreamReady === undefined;
  197. /*
  198. * As the first thing on the connection, expect a greeting packet from
  199. * the connection's root actor.
  200. */
  201. this.mainRoot = null;
  202. this.expectReply("root", (aPacket) => {
  203. this.mainRoot = new RootClient(this, aPacket);
  204. this.emit("connected", aPacket.applicationType, aPacket.traits);
  205. });
  206. };
  207. /**
  208. * A declarative helper for defining methods that send requests to the server.
  209. *
  210. * @param aPacketSkeleton
  211. * The form of the packet to send. Can specify fields to be filled from
  212. * the parameters by using the |args| function.
  213. * @param before
  214. * The function to call before sending the packet. Is passed the packet,
  215. * and the return value is used as the new packet. The |this| context is
  216. * the instance of the client object we are defining a method for.
  217. * @param after
  218. * The function to call after the response is received. It is passed the
  219. * response, and the return value is considered the new response that
  220. * will be passed to the callback. The |this| context is the instance of
  221. * the client object we are defining a method for.
  222. * @return Request
  223. * The `Request` object that is a Promise object and resolves once
  224. * we receive the response. (See request method for more details)
  225. */
  226. DebuggerClient.requester = function (aPacketSkeleton, config = {}) {
  227. let { before, after } = config;
  228. return DevToolsUtils.makeInfallible(function (...args) {
  229. let outgoingPacket = {
  230. to: aPacketSkeleton.to || this.actor
  231. };
  232. let maxPosition = -1;
  233. for (let k of Object.keys(aPacketSkeleton)) {
  234. if (aPacketSkeleton[k] instanceof DebuggerClient.Argument) {
  235. let { position } = aPacketSkeleton[k];
  236. outgoingPacket[k] = aPacketSkeleton[k].getArgument(args);
  237. maxPosition = Math.max(position, maxPosition);
  238. } else {
  239. outgoingPacket[k] = aPacketSkeleton[k];
  240. }
  241. }
  242. if (before) {
  243. outgoingPacket = before.call(this, outgoingPacket);
  244. }
  245. return this.request(outgoingPacket, DevToolsUtils.makeInfallible((aResponse) => {
  246. if (after) {
  247. let { from } = aResponse;
  248. aResponse = after.call(this, aResponse);
  249. if (!aResponse.from) {
  250. aResponse.from = from;
  251. }
  252. }
  253. // The callback is always the last parameter.
  254. let thisCallback = args[maxPosition + 1];
  255. if (thisCallback) {
  256. thisCallback(aResponse);
  257. }
  258. }, "DebuggerClient.requester request callback"));
  259. }, "DebuggerClient.requester");
  260. };
  261. function args(aPos) {
  262. return new DebuggerClient.Argument(aPos);
  263. }
  264. DebuggerClient.Argument = function (aPosition) {
  265. this.position = aPosition;
  266. };
  267. DebuggerClient.Argument.prototype.getArgument = function (aParams) {
  268. if (!(this.position in aParams)) {
  269. throw new Error("Bad index into params: " + this.position);
  270. }
  271. return aParams[this.position];
  272. };
  273. // Expose these to save callers the trouble of importing DebuggerSocket
  274. DebuggerClient.socketConnect = function (options) {
  275. // Defined here instead of just copying the function to allow lazy-load
  276. return DebuggerSocket.connect(options);
  277. };
  278. DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
  279. return Authentication.Authenticators;
  280. });
  281. DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
  282. return Authentication.AuthenticationResult;
  283. });
  284. DebuggerClient.prototype = {
  285. /**
  286. * Connect to the server and start exchanging protocol messages.
  287. *
  288. * @param aOnConnected function
  289. * If specified, will be called when the greeting packet is
  290. * received from the debugging server.
  291. *
  292. * @return Promise
  293. * Resolves once connected with an array whose first element
  294. * is the application type, by default "browser", and the second
  295. * element is the traits object (help figure out the features
  296. * and behaviors of the server we connect to. See RootActor).
  297. */
  298. connect: function (aOnConnected) {
  299. let deferred = promise.defer();
  300. this.emit("connect");
  301. // Also emit the event on the |DebuggerClient| object (not on the instance),
  302. // so it's possible to track all instances.
  303. events.emit(DebuggerClient, "connect", this);
  304. this.addOneTimeListener("connected", (aName, aApplicationType, aTraits) => {
  305. this.traits = aTraits;
  306. if (aOnConnected) {
  307. aOnConnected(aApplicationType, aTraits);
  308. }
  309. deferred.resolve([aApplicationType, aTraits]);
  310. });
  311. this._transport.ready();
  312. return deferred.promise;
  313. },
  314. /**
  315. * Shut down communication with the debugging server.
  316. *
  317. * @param aOnClosed function
  318. * If specified, will be called when the debugging connection
  319. * has been closed. This parameter is deprecated - please use
  320. * the returned Promise.
  321. * @return Promise
  322. * Resolves after the underlying transport is closed.
  323. */
  324. close: function (aOnClosed) {
  325. let deferred = promise.defer();
  326. if (aOnClosed) {
  327. deferred.promise.then(aOnClosed);
  328. }
  329. // Disable detach event notifications, because event handlers will be in a
  330. // cleared scope by the time they run.
  331. this._eventsEnabled = false;
  332. let cleanup = () => {
  333. this._transport.close();
  334. this._transport = null;
  335. };
  336. // If the connection is already closed,
  337. // there is no need to detach client
  338. // as we won't be able to send any message.
  339. if (this._closed) {
  340. cleanup();
  341. deferred.resolve();
  342. return deferred.promise;
  343. }
  344. this.addOneTimeListener("closed", deferred.resolve);
  345. // Call each client's `detach` method by calling
  346. // lastly registered ones first to give a chance
  347. // to detach child clients first.
  348. let clients = [...this._clients.values()];
  349. this._clients.clear();
  350. const detachClients = () => {
  351. let client = clients.pop();
  352. if (!client) {
  353. // All clients detached.
  354. cleanup();
  355. return;
  356. }
  357. if (client.detach) {
  358. client.detach(detachClients);
  359. return;
  360. }
  361. detachClients();
  362. };
  363. detachClients();
  364. return deferred.promise;
  365. },
  366. /*
  367. * This function exists only to preserve DebuggerClient's interface;
  368. * new code should say 'client.mainRoot.listTabs()'.
  369. */
  370. listTabs: function (aOnResponse) { return this.mainRoot.listTabs(aOnResponse); },
  371. /*
  372. * This function exists only to preserve DebuggerClient's interface;
  373. * new code should say 'client.mainRoot.listAddons()'.
  374. */
  375. listAddons: function (aOnResponse) { return this.mainRoot.listAddons(aOnResponse); },
  376. getTab: function (aFilter) { return this.mainRoot.getTab(aFilter); },
  377. /**
  378. * Attach to a tab actor.
  379. *
  380. * @param string aTabActor
  381. * The actor ID for the tab to attach.
  382. * @param function aOnResponse
  383. * Called with the response packet and a TabClient
  384. * (which will be undefined on error).
  385. */
  386. attachTab: function (aTabActor, aOnResponse = noop) {
  387. if (this._clients.has(aTabActor)) {
  388. let cachedTab = this._clients.get(aTabActor);
  389. let cachedResponse = {
  390. cacheDisabled: cachedTab.cacheDisabled,
  391. javascriptEnabled: cachedTab.javascriptEnabled,
  392. traits: cachedTab.traits,
  393. };
  394. DevToolsUtils.executeSoon(() => aOnResponse(cachedResponse, cachedTab));
  395. return promise.resolve([cachedResponse, cachedTab]);
  396. }
  397. let packet = {
  398. to: aTabActor,
  399. type: "attach"
  400. };
  401. return this.request(packet).then(aResponse => {
  402. let tabClient;
  403. if (!aResponse.error) {
  404. tabClient = new TabClient(this, aResponse);
  405. this.registerClient(tabClient);
  406. }
  407. aOnResponse(aResponse, tabClient);
  408. return [aResponse, tabClient];
  409. });
  410. },
  411. attachWorker: function DC_attachWorker(aWorkerActor, aOnResponse = noop) {
  412. let workerClient = this._clients.get(aWorkerActor);
  413. if (workerClient !== undefined) {
  414. let response = {
  415. from: workerClient.actor,
  416. type: "attached",
  417. url: workerClient.url
  418. };
  419. DevToolsUtils.executeSoon(() => aOnResponse(response, workerClient));
  420. return promise.resolve([response, workerClient]);
  421. }
  422. return this.request({ to: aWorkerActor, type: "attach" }).then(aResponse => {
  423. if (aResponse.error) {
  424. aOnResponse(aResponse, null);
  425. return [aResponse, null];
  426. }
  427. let workerClient = new WorkerClient(this, aResponse);
  428. this.registerClient(workerClient);
  429. aOnResponse(aResponse, workerClient);
  430. return [aResponse, workerClient];
  431. });
  432. },
  433. /**
  434. * Attach to an addon actor.
  435. *
  436. * @param string aAddonActor
  437. * The actor ID for the addon to attach.
  438. * @param function aOnResponse
  439. * Called with the response packet and a AddonClient
  440. * (which will be undefined on error).
  441. */
  442. attachAddon: function DC_attachAddon(aAddonActor, aOnResponse = noop) {
  443. let packet = {
  444. to: aAddonActor,
  445. type: "attach"
  446. };
  447. return this.request(packet).then(aResponse => {
  448. let addonClient;
  449. if (!aResponse.error) {
  450. addonClient = new AddonClient(this, aAddonActor);
  451. this.registerClient(addonClient);
  452. this.activeAddon = addonClient;
  453. }
  454. aOnResponse(aResponse, addonClient);
  455. return [aResponse, addonClient];
  456. });
  457. },
  458. /**
  459. * Attach to a Web Console actor.
  460. *
  461. * @param string aConsoleActor
  462. * The ID for the console actor to attach to.
  463. * @param array aListeners
  464. * The console listeners you want to start.
  465. * @param function aOnResponse
  466. * Called with the response packet and a WebConsoleClient
  467. * instance (which will be undefined on error).
  468. */
  469. attachConsole:
  470. function (aConsoleActor, aListeners, aOnResponse = noop) {
  471. let packet = {
  472. to: aConsoleActor,
  473. type: "startListeners",
  474. listeners: aListeners,
  475. };
  476. return this.request(packet).then(aResponse => {
  477. let consoleClient;
  478. if (!aResponse.error) {
  479. if (this._clients.has(aConsoleActor)) {
  480. consoleClient = this._clients.get(aConsoleActor);
  481. } else {
  482. consoleClient = new WebConsoleClient(this, aResponse);
  483. this.registerClient(consoleClient);
  484. }
  485. }
  486. aOnResponse(aResponse, consoleClient);
  487. return [aResponse, consoleClient];
  488. });
  489. },
  490. /**
  491. * Attach to a global-scoped thread actor for chrome debugging.
  492. *
  493. * @param string aThreadActor
  494. * The actor ID for the thread to attach.
  495. * @param function aOnResponse
  496. * Called with the response packet and a ThreadClient
  497. * (which will be undefined on error).
  498. * @param object aOptions
  499. * Configuration options.
  500. * - useSourceMaps: whether to use source maps or not.
  501. */
  502. attachThread: function (aThreadActor, aOnResponse = noop, aOptions = {}) {
  503. if (this._clients.has(aThreadActor)) {
  504. let client = this._clients.get(aThreadActor);
  505. DevToolsUtils.executeSoon(() => aOnResponse({}, client));
  506. return promise.resolve([{}, client]);
  507. }
  508. let packet = {
  509. to: aThreadActor,
  510. type: "attach",
  511. options: aOptions
  512. };
  513. return this.request(packet).then(aResponse => {
  514. if (!aResponse.error) {
  515. var threadClient = new ThreadClient(this, aThreadActor);
  516. this.registerClient(threadClient);
  517. }
  518. aOnResponse(aResponse, threadClient);
  519. return [aResponse, threadClient];
  520. });
  521. },
  522. /**
  523. * Attach to a trace actor.
  524. *
  525. * @param string aTraceActor
  526. * The actor ID for the tracer to attach.
  527. * @param function aOnResponse
  528. * Called with the response packet and a TraceClient
  529. * (which will be undefined on error).
  530. */
  531. attachTracer: function (aTraceActor, aOnResponse = noop) {
  532. if (this._clients.has(aTraceActor)) {
  533. let client = this._clients.get(aTraceActor);
  534. DevToolsUtils.executeSoon(() => aOnResponse({}, client));
  535. return promise.resolve([{}, client]);
  536. }
  537. let packet = {
  538. to: aTraceActor,
  539. type: "attach"
  540. };
  541. return this.request(packet).then(aResponse => {
  542. if (!aResponse.error) {
  543. var traceClient = new TraceClient(this, aTraceActor);
  544. this.registerClient(traceClient);
  545. }
  546. aOnResponse(aResponse, traceClient);
  547. return [aResponse, traceClient];
  548. });
  549. },
  550. /**
  551. * Fetch the ChromeActor for the main process or ChildProcessActor for a
  552. * a given child process ID.
  553. *
  554. * @param number aId
  555. * The ID for the process to attach (returned by `listProcesses`).
  556. * Connected to the main process if omitted, or is 0.
  557. */
  558. getProcess: function (aId) {
  559. let packet = {
  560. to: "root",
  561. type: "getProcess"
  562. };
  563. if (typeof (aId) == "number") {
  564. packet.id = aId;
  565. }
  566. return this.request(packet);
  567. },
  568. /**
  569. * Release an object actor.
  570. *
  571. * @param string aActor
  572. * The actor ID to send the request to.
  573. * @param aOnResponse function
  574. * If specified, will be called with the response packet when
  575. * debugging server responds.
  576. */
  577. release: DebuggerClient.requester({
  578. to: args(0),
  579. type: "release"
  580. }),
  581. /**
  582. * Send a request to the debugging server.
  583. *
  584. * @param aRequest object
  585. * A JSON packet to send to the debugging server.
  586. * @param aOnResponse function
  587. * If specified, will be called with the JSON response packet when
  588. * debugging server responds.
  589. * @return Request
  590. * This object emits a number of events to allow you to respond to
  591. * different parts of the request lifecycle.
  592. * It is also a Promise object, with a `then` method, that is resolved
  593. * whenever a JSON or a Bulk response is received; and is rejected
  594. * if the response is an error.
  595. * Note: This return value can be ignored if you are using JSON alone,
  596. * because the callback provided in |aOnResponse| will be bound to the
  597. * "json-reply" event automatically.
  598. *
  599. * Events emitted:
  600. * * json-reply: The server replied with a JSON packet, which is
  601. * passed as event data.
  602. * * bulk-reply: The server replied with bulk data, which you can read
  603. * using the event data object containing:
  604. * * actor: Name of actor that received the packet
  605. * * type: Name of actor's method that was called on receipt
  606. * * length: Size of the data to be read
  607. * * stream: This input stream should only be used directly if you
  608. * can ensure that you will read exactly |length| bytes
  609. * and will not close the stream when reading is complete
  610. * * done: If you use the stream directly (instead of |copyTo|
  611. * below), you must signal completion by resolving /
  612. * rejecting this deferred. If it's rejected, the
  613. * transport will be closed. If an Error is supplied as a
  614. * rejection value, it will be logged via |dumpn|. If you
  615. * do use |copyTo|, resolving is taken care of for you
  616. * when copying completes.
  617. * * copyTo: A helper function for getting your data out of the
  618. * stream that meets the stream handling requirements
  619. * above, and has the following signature:
  620. * @param output nsIAsyncOutputStream
  621. * The stream to copy to.
  622. * @return Promise
  623. * The promise is resolved when copying completes or
  624. * rejected if any (unexpected) errors occur.
  625. * This object also emits "progress" events for each chunk
  626. * that is copied. See stream-utils.js.
  627. */
  628. request: function (aRequest, aOnResponse) {
  629. if (!this.mainRoot) {
  630. throw Error("Have not yet received a hello packet from the server.");
  631. }
  632. let type = aRequest.type || "";
  633. if (!aRequest.to) {
  634. throw Error("'" + type + "' request packet has no destination.");
  635. }
  636. if (this._closed) {
  637. let msg = "'" + type + "' request packet to " +
  638. "'" + aRequest.to + "' " +
  639. "can't be sent as the connection is closed.";
  640. let resp = { error: "connectionClosed", message: msg };
  641. if (aOnResponse) {
  642. aOnResponse(resp);
  643. }
  644. return promise.reject(resp);
  645. }
  646. let request = new Request(aRequest);
  647. request.format = "json";
  648. request.stack = getStack();
  649. if (aOnResponse) {
  650. request.on("json-reply", aOnResponse);
  651. }
  652. this._sendOrQueueRequest(request);
  653. // Implement a Promise like API on the returned object
  654. // that resolves/rejects on request response
  655. let deferred = promise.defer();
  656. function listenerJson(resp) {
  657. request.off("json-reply", listenerJson);
  658. request.off("bulk-reply", listenerBulk);
  659. if (resp.error) {
  660. deferred.reject(resp);
  661. } else {
  662. deferred.resolve(resp);
  663. }
  664. }
  665. function listenerBulk(resp) {
  666. request.off("json-reply", listenerJson);
  667. request.off("bulk-reply", listenerBulk);
  668. deferred.resolve(resp);
  669. }
  670. request.on("json-reply", listenerJson);
  671. request.on("bulk-reply", listenerBulk);
  672. request.then = deferred.promise.then.bind(deferred.promise);
  673. return request;
  674. },
  675. /**
  676. * Transmit streaming data via a bulk request.
  677. *
  678. * This method initiates the bulk send process by queuing up the header data.
  679. * The caller receives eventual access to a stream for writing.
  680. *
  681. * Since this opens up more options for how the server might respond (it could
  682. * send back either JSON or bulk data), and the returned Request object emits
  683. * events for different stages of the request process that you may want to
  684. * react to.
  685. *
  686. * @param request Object
  687. * This is modeled after the format of JSON packets above, but does not
  688. * actually contain the data, but is instead just a routing header:
  689. * * actor: Name of actor that will receive the packet
  690. * * type: Name of actor's method that should be called on receipt
  691. * * length: Size of the data to be sent
  692. * @return Request
  693. * This object emits a number of events to allow you to respond to
  694. * different parts of the request lifecycle.
  695. *
  696. * Events emitted:
  697. * * bulk-send-ready: Ready to send bulk data to the server, using the
  698. * event data object containing:
  699. * * stream: This output stream should only be used directly if
  700. * you can ensure that you will write exactly |length|
  701. * bytes and will not close the stream when writing is
  702. * complete
  703. * * done: If you use the stream directly (instead of |copyFrom|
  704. * below), you must signal completion by resolving /
  705. * rejecting this deferred. If it's rejected, the
  706. * transport will be closed. If an Error is supplied as
  707. * a rejection value, it will be logged via |dumpn|. If
  708. * you do use |copyFrom|, resolving is taken care of for
  709. * you when copying completes.
  710. * * copyFrom: A helper function for getting your data onto the
  711. * stream that meets the stream handling requirements
  712. * above, and has the following signature:
  713. * @param input nsIAsyncInputStream
  714. * The stream to copy from.
  715. * @return Promise
  716. * The promise is resolved when copying completes or
  717. * rejected if any (unexpected) errors occur.
  718. * This object also emits "progress" events for each chunk
  719. * that is copied. See stream-utils.js.
  720. * * json-reply: The server replied with a JSON packet, which is
  721. * passed as event data.
  722. * * bulk-reply: The server replied with bulk data, which you can read
  723. * using the event data object containing:
  724. * * actor: Name of actor that received the packet
  725. * * type: Name of actor's method that was called on receipt
  726. * * length: Size of the data to be read
  727. * * stream: This input stream should only be used directly if you
  728. * can ensure that you will read exactly |length| bytes
  729. * and will not close the stream when reading is complete
  730. * * done: If you use the stream directly (instead of |copyTo|
  731. * below), you must signal completion by resolving /
  732. * rejecting this deferred. If it's rejected, the
  733. * transport will be closed. If an Error is supplied as a
  734. * rejection value, it will be logged via |dumpn|. If you
  735. * do use |copyTo|, resolving is taken care of for you
  736. * when copying completes.
  737. * * copyTo: A helper function for getting your data out of the
  738. * stream that meets the stream handling requirements
  739. * above, and has the following signature:
  740. * @param output nsIAsyncOutputStream
  741. * The stream to copy to.
  742. * @return Promise
  743. * The promise is resolved when copying completes or
  744. * rejected if any (unexpected) errors occur.
  745. * This object also emits "progress" events for each chunk
  746. * that is copied. See stream-utils.js.
  747. */
  748. startBulkRequest: function (request) {
  749. if (!this.traits.bulk) {
  750. throw Error("Server doesn't support bulk transfers");
  751. }
  752. if (!this.mainRoot) {
  753. throw Error("Have not yet received a hello packet from the server.");
  754. }
  755. if (!request.type) {
  756. throw Error("Bulk packet is missing the required 'type' field.");
  757. }
  758. if (!request.actor) {
  759. throw Error("'" + request.type + "' bulk packet has no destination.");
  760. }
  761. if (!request.length) {
  762. throw Error("'" + request.type + "' bulk packet has no length.");
  763. }
  764. request = new Request(request);
  765. request.format = "bulk";
  766. this._sendOrQueueRequest(request);
  767. return request;
  768. },
  769. /**
  770. * If a new request can be sent immediately, do so. Otherwise, queue it.
  771. */
  772. _sendOrQueueRequest(request) {
  773. let actor = request.actor;
  774. if (!this._activeRequests.has(actor)) {
  775. this._sendRequest(request);
  776. } else {
  777. this._queueRequest(request);
  778. }
  779. },
  780. /**
  781. * Send a request.
  782. * @throws Error if there is already an active request in flight for the same
  783. * actor.
  784. */
  785. _sendRequest(request) {
  786. let actor = request.actor;
  787. this.expectReply(actor, request);
  788. if (request.format === "json") {
  789. this._transport.send(request.request);
  790. return false;
  791. }
  792. this._transport.startBulkSend(request.request).then((...args) => {
  793. request.emit("bulk-send-ready", ...args);
  794. });
  795. },
  796. /**
  797. * Queue a request to be sent later. Queues are only drained when an in
  798. * flight request to a given actor completes.
  799. */
  800. _queueRequest(request) {
  801. let actor = request.actor;
  802. let queue = this._pendingRequests.get(actor) || [];
  803. queue.push(request);
  804. this._pendingRequests.set(actor, queue);
  805. },
  806. /**
  807. * Attempt the next request to a given actor (if any).
  808. */
  809. _attemptNextRequest(actor) {
  810. if (this._activeRequests.has(actor)) {
  811. return;
  812. }
  813. let queue = this._pendingRequests.get(actor);
  814. if (!queue) {
  815. return;
  816. }
  817. let request = queue.shift();
  818. if (queue.length === 0) {
  819. this._pendingRequests.delete(actor);
  820. }
  821. this._sendRequest(request);
  822. },
  823. /**
  824. * Arrange to hand the next reply from |aActor| to the handler bound to
  825. * |aRequest|.
  826. *
  827. * DebuggerClient.prototype.request / startBulkRequest usually takes care of
  828. * establishing the handler for a given request, but in rare cases (well,
  829. * greetings from new root actors, is the only case at the moment) we must be
  830. * prepared for a "reply" that doesn't correspond to any request we sent.
  831. */
  832. expectReply: function (aActor, aRequest) {
  833. if (this._activeRequests.has(aActor)) {
  834. throw Error("clashing handlers for next reply from " + uneval(aActor));
  835. }
  836. // If a handler is passed directly (as it is with the handler for the root
  837. // actor greeting), create a dummy request to bind this to.
  838. if (typeof aRequest === "function") {
  839. let handler = aRequest;
  840. aRequest = new Request();
  841. aRequest.on("json-reply", handler);
  842. }
  843. this._activeRequests.set(aActor, aRequest);
  844. },
  845. // Transport hooks.
  846. /**
  847. * Called by DebuggerTransport to dispatch incoming packets as appropriate.
  848. *
  849. * @param aPacket object
  850. * The incoming packet.
  851. */
  852. onPacket: function (aPacket) {
  853. if (!aPacket.from) {
  854. DevToolsUtils.reportException(
  855. "onPacket",
  856. new Error("Server did not specify an actor, dropping packet: " +
  857. JSON.stringify(aPacket)));
  858. return;
  859. }
  860. // If we have a registered Front for this actor, let it handle the packet
  861. // and skip all the rest of this unpleasantness.
  862. let front = this.getActor(aPacket.from);
  863. if (front) {
  864. front.onPacket(aPacket);
  865. return;
  866. }
  867. // Check for "forwardingCancelled" here instead of using a client to handle it.
  868. // This is necessary because we might receive this event while the client is closing,
  869. // and the clients have already been removed by that point.
  870. if (this.mainRoot &&
  871. aPacket.from == this.mainRoot.actor &&
  872. aPacket.type == "forwardingCancelled") {
  873. this.purgeRequests(aPacket.prefix);
  874. return;
  875. }
  876. if (this._clients.has(aPacket.from) && aPacket.type) {
  877. let client = this._clients.get(aPacket.from);
  878. let type = aPacket.type;
  879. if (client.events.indexOf(type) != -1) {
  880. client.emit(type, aPacket);
  881. // we ignore the rest, as the client is expected to handle this packet.
  882. return;
  883. }
  884. }
  885. let activeRequest;
  886. // See if we have a handler function waiting for a reply from this
  887. // actor. (Don't count unsolicited notifications or pauses as
  888. // replies.)
  889. if (this._activeRequests.has(aPacket.from) &&
  890. !(aPacket.type in UnsolicitedNotifications) &&
  891. !(aPacket.type == ThreadStateTypes.paused &&
  892. aPacket.why.type in UnsolicitedPauses)) {
  893. activeRequest = this._activeRequests.get(aPacket.from);
  894. this._activeRequests.delete(aPacket.from);
  895. }
  896. // If there is a subsequent request for the same actor, hand it off to the
  897. // transport. Delivery of packets on the other end is always async, even
  898. // in the local transport case.
  899. this._attemptNextRequest(aPacket.from);
  900. // Packets that indicate thread state changes get special treatment.
  901. if (aPacket.type in ThreadStateTypes &&
  902. this._clients.has(aPacket.from) &&
  903. typeof this._clients.get(aPacket.from)._onThreadState == "function") {
  904. this._clients.get(aPacket.from)._onThreadState(aPacket);
  905. }
  906. // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
  907. if (!this.traits.noNeedToFakeResumptionOnNavigation) {
  908. // On navigation the server resumes, so the client must resume as well.
  909. // We achieve that by generating a fake resumption packet that triggers
  910. // the client's thread state change listeners.
  911. if (aPacket.type == UnsolicitedNotifications.tabNavigated &&
  912. this._clients.has(aPacket.from) &&
  913. this._clients.get(aPacket.from).thread) {
  914. let thread = this._clients.get(aPacket.from).thread;
  915. let resumption = { from: thread._actor, type: "resumed" };
  916. thread._onThreadState(resumption);
  917. }
  918. }
  919. // Only try to notify listeners on events, not responses to requests
  920. // that lack a packet type.
  921. if (aPacket.type) {
  922. this.emit(aPacket.type, aPacket);
  923. }
  924. if (activeRequest) {
  925. let emitReply = () => activeRequest.emit("json-reply", aPacket);
  926. if (activeRequest.stack) {
  927. callFunctionWithAsyncStack(emitReply, activeRequest.stack,
  928. "DevTools RDP");
  929. } else {
  930. emitReply();
  931. }
  932. }
  933. },
  934. /**
  935. * Called by the DebuggerTransport to dispatch incoming bulk packets as
  936. * appropriate.
  937. *
  938. * @param packet object
  939. * The incoming packet, which contains:
  940. * * actor: Name of actor that will receive the packet
  941. * * type: Name of actor's method that should be called on receipt
  942. * * length: Size of the data to be read
  943. * * stream: This input stream should only be used directly if you can
  944. * ensure that you will read exactly |length| bytes and will
  945. * not close the stream when reading is complete
  946. * * done: If you use the stream directly (instead of |copyTo|
  947. * below), you must signal completion by resolving /
  948. * rejecting this deferred. If it's rejected, the transport
  949. * will be closed. If an Error is supplied as a rejection
  950. * value, it will be logged via |dumpn|. If you do use
  951. * |copyTo|, resolving is taken care of for you when copying
  952. * completes.
  953. * * copyTo: A helper function for getting your data out of the stream
  954. * that meets the stream handling requirements above, and has
  955. * the following signature:
  956. * @param output nsIAsyncOutputStream
  957. * The stream to copy to.
  958. * @return Promise
  959. * The promise is resolved when copying completes or rejected
  960. * if any (unexpected) errors occur.
  961. * This object also emits "progress" events for each chunk
  962. * that is copied. See stream-utils.js.
  963. */
  964. onBulkPacket: function (packet) {
  965. let { actor, type, length } = packet;
  966. if (!actor) {
  967. DevToolsUtils.reportException(
  968. "onBulkPacket",
  969. new Error("Server did not specify an actor, dropping bulk packet: " +
  970. JSON.stringify(packet)));
  971. return;
  972. }
  973. // See if we have a handler function waiting for a reply from this
  974. // actor.
  975. if (!this._activeRequests.has(actor)) {
  976. return;
  977. }
  978. let activeRequest = this._activeRequests.get(actor);
  979. this._activeRequests.delete(actor);
  980. // If there is a subsequent request for the same actor, hand it off to the
  981. // transport. Delivery of packets on the other end is always async, even
  982. // in the local transport case.
  983. this._attemptNextRequest(actor);
  984. activeRequest.emit("bulk-reply", packet);
  985. },
  986. /**
  987. * Called by DebuggerTransport when the underlying stream is closed.
  988. *
  989. * @param aStatus nsresult
  990. * The status code that corresponds to the reason for closing
  991. * the stream.
  992. */
  993. onClosed: function () {
  994. this._closed = true;
  995. this.emit("closed");
  996. this.purgeRequests();
  997. // The |_pools| array on the client-side currently is used only by
  998. // protocol.js to store active fronts, mirroring the actor pools found in
  999. // the server. So, read all usages of "pool" as "protocol.js front".
  1000. //
  1001. // In the normal case where we shutdown cleanly, the toolbox tells each tool
  1002. // to close, and they each call |destroy| on any fronts they were using.
  1003. // When |destroy| or |cleanup| is called on a protocol.js front, it also
  1004. // removes itself from the |_pools| array. Once the toolbox has shutdown,
  1005. // the connection is closed, and we reach here. All fronts (should have
  1006. // been) |destroy|ed, so |_pools| should empty.
  1007. //
  1008. // If the connection instead aborts unexpectedly, we may end up here with
  1009. // all fronts used during the life of the connection. So, we call |cleanup|
  1010. // on them clear their state, reject pending requests, and remove themselves
  1011. // from |_pools|. This saves the toolbox from hanging indefinitely, in case
  1012. // it waits for some server response before shutdown that will now never
  1013. // arrive.
  1014. for (let pool of this._pools) {
  1015. pool.cleanup();
  1016. }
  1017. },
  1018. /**
  1019. * Purge pending and active requests in this client.
  1020. *
  1021. * @param prefix string (optional)
  1022. * If a prefix is given, only requests for actor IDs that start with the prefix
  1023. * will be cleaned up. This is useful when forwarding of a portion of requests
  1024. * is cancelled on the server.
  1025. */
  1026. purgeRequests(prefix = "") {
  1027. let reject = function (type, request) {
  1028. // Server can send packets on its own and client only pass a callback
  1029. // to expectReply, so that there is no request object.
  1030. let msg;
  1031. if (request.request) {
  1032. msg = "'" + request.request.type + "' " + type + " request packet" +
  1033. " to '" + request.actor + "' " +
  1034. "can't be sent as the connection just closed.";
  1035. } else {
  1036. msg = "server side packet can't be received as the connection just closed.";
  1037. }
  1038. let packet = { error: "connectionClosed", message: msg };
  1039. request.emit("json-reply", packet);
  1040. };
  1041. let pendingRequestsToReject = [];
  1042. this._pendingRequests.forEach((requests, actor) => {
  1043. if (!actor.startsWith(prefix)) {
  1044. return;
  1045. }
  1046. this._pendingRequests.delete(actor);
  1047. pendingRequestsToReject = pendingRequestsToReject.concat(requests);
  1048. });
  1049. pendingRequestsToReject.forEach(request => reject("pending", request));
  1050. let activeRequestsToReject = [];
  1051. this._activeRequests.forEach((request, actor) => {
  1052. if (!actor.startsWith(prefix)) {
  1053. return;
  1054. }
  1055. this._activeRequests.delete(actor);
  1056. activeRequestsToReject = activeRequestsToReject.concat(request);
  1057. });
  1058. activeRequestsToReject.forEach(request => reject("active", request));
  1059. },
  1060. registerClient: function (client) {
  1061. let actorID = client.actor;
  1062. if (!actorID) {
  1063. throw new Error("DebuggerServer.registerClient expects " +
  1064. "a client instance with an `actor` attribute.");
  1065. }
  1066. if (!Array.isArray(client.events)) {
  1067. throw new Error("DebuggerServer.registerClient expects " +
  1068. "a client instance with an `events` attribute " +
  1069. "that is an array.");
  1070. }
  1071. if (client.events.length > 0 && typeof (client.emit) != "function") {
  1072. throw new Error("DebuggerServer.registerClient expects " +
  1073. "a client instance with non-empty `events` array to" +
  1074. "have an `emit` function.");
  1075. }
  1076. if (this._clients.has(actorID)) {
  1077. throw new Error("DebuggerServer.registerClient already registered " +
  1078. "a client for this actor.");
  1079. }
  1080. this._clients.set(actorID, client);
  1081. },
  1082. unregisterClient: function (client) {
  1083. let actorID = client.actor;
  1084. if (!actorID) {
  1085. throw new Error("DebuggerServer.unregisterClient expects " +
  1086. "a Client instance with a `actor` attribute.");
  1087. }
  1088. this._clients.delete(actorID);
  1089. },
  1090. /**
  1091. * Actor lifetime management, echos the server's actor pools.
  1092. */
  1093. __pools: null,
  1094. get _pools() {
  1095. if (this.__pools) {
  1096. return this.__pools;
  1097. }
  1098. this.__pools = new Set();
  1099. return this.__pools;
  1100. },
  1101. addActorPool: function (pool) {
  1102. this._pools.add(pool);
  1103. },
  1104. removeActorPool: function (pool) {
  1105. this._pools.delete(pool);
  1106. },
  1107. getActor: function (actorID) {
  1108. let pool = this.poolFor(actorID);
  1109. return pool ? pool.get(actorID) : null;
  1110. },
  1111. poolFor: function (actorID) {
  1112. for (let pool of this._pools) {
  1113. if (pool.has(actorID)) return pool;
  1114. }
  1115. return null;
  1116. },
  1117. /**
  1118. * Currently attached addon.
  1119. */
  1120. activeAddon: null
  1121. };
  1122. eventSource(DebuggerClient.prototype);
  1123. function Request(request) {
  1124. this.request = request;
  1125. }
  1126. Request.prototype = {
  1127. on: function (type, listener) {
  1128. events.on(this, type, listener);
  1129. },
  1130. off: function (type, listener) {
  1131. events.off(this, type, listener);
  1132. },
  1133. once: function (type, listener) {
  1134. events.once(this, type, listener);
  1135. },
  1136. emit: function (type, ...args) {
  1137. events.emit(this, type, ...args);
  1138. },
  1139. get actor() { return this.request.to || this.request.actor; }
  1140. };
  1141. /**
  1142. * Creates a tab client for the remote debugging protocol server. This client
  1143. * is a front to the tab actor created in the server side, hiding the protocol
  1144. * details in a traditional JavaScript API.
  1145. *
  1146. * @param aClient DebuggerClient
  1147. * The debugger client parent.
  1148. * @param aForm object
  1149. * The protocol form for this tab.
  1150. */
  1151. function TabClient(aClient, aForm) {
  1152. this.client = aClient;
  1153. this._actor = aForm.from;
  1154. this._threadActor = aForm.threadActor;
  1155. this.javascriptEnabled = aForm.javascriptEnabled;
  1156. this.cacheDisabled = aForm.cacheDisabled;
  1157. this.thread = null;
  1158. this.request = this.client.request;
  1159. this.traits = aForm.traits || {};
  1160. this.events = ["workerListChanged"];
  1161. }
  1162. TabClient.prototype = {
  1163. get actor() { return this._actor; },
  1164. get _transport() { return this.client._transport; },
  1165. /**
  1166. * Attach to a thread actor.
  1167. *
  1168. * @param object aOptions
  1169. * Configuration options.
  1170. * - useSourceMaps: whether to use source maps or not.
  1171. * @param function aOnResponse
  1172. * Called with the response packet and a ThreadClient
  1173. * (which will be undefined on error).
  1174. */
  1175. attachThread: function (aOptions = {}, aOnResponse = noop) {
  1176. if (this.thread) {
  1177. DevToolsUtils.executeSoon(() => aOnResponse({}, this.thread));
  1178. return promise.resolve([{}, this.thread]);
  1179. }
  1180. let packet = {
  1181. to: this._threadActor,
  1182. type: "attach",
  1183. options: aOptions
  1184. };
  1185. return this.request(packet).then(aResponse => {
  1186. if (!aResponse.error) {
  1187. this.thread = new ThreadClient(this, this._threadActor);
  1188. this.client.registerClient(this.thread);
  1189. }
  1190. aOnResponse(aResponse, this.thread);
  1191. return [aResponse, this.thread];
  1192. });
  1193. },
  1194. /**
  1195. * Detach the client from the tab actor.
  1196. *
  1197. * @param function aOnResponse
  1198. * Called with the response packet.
  1199. */
  1200. detach: DebuggerClient.requester({
  1201. type: "detach"
  1202. }, {
  1203. before: function (aPacket) {
  1204. if (this.thread) {
  1205. this.thread.detach();
  1206. }
  1207. return aPacket;
  1208. },
  1209. after: function (aResponse) {
  1210. this.client.unregisterClient(this);
  1211. return aResponse;
  1212. },
  1213. }),
  1214. /**
  1215. * Bring the window to the front.
  1216. */
  1217. focus: DebuggerClient.requester({
  1218. type: "focus"
  1219. }, {}),
  1220. /**
  1221. * Reload the page in this tab.
  1222. *
  1223. * @param [optional] object options
  1224. * An object with a `force` property indicating whether or not
  1225. * this reload should skip the cache
  1226. */
  1227. reload: function (options = { force: false }) {
  1228. return this._reload(options);
  1229. },
  1230. _reload: DebuggerClient.requester({
  1231. type: "reload",
  1232. options: args(0)
  1233. }),
  1234. /**
  1235. * Navigate to another URL.
  1236. *
  1237. * @param string url
  1238. * The URL to navigate to.
  1239. */
  1240. navigateTo: DebuggerClient.requester({
  1241. type: "navigateTo",
  1242. url: args(0)
  1243. }),
  1244. /**
  1245. * Reconfigure the tab actor.
  1246. *
  1247. * @param object aOptions
  1248. * A dictionary object of the new options to use in the tab actor.
  1249. * @param function aOnResponse
  1250. * Called with the response packet.
  1251. */
  1252. reconfigure: DebuggerClient.requester({
  1253. type: "reconfigure",
  1254. options: args(0)
  1255. }),
  1256. listWorkers: DebuggerClient.requester({
  1257. type: "listWorkers"
  1258. }),
  1259. attachWorker: function (aWorkerActor, aOnResponse) {
  1260. this.client.attachWorker(aWorkerActor, aOnResponse);
  1261. },
  1262. /**
  1263. * Resolve a location ({ url, line, column }) to its current
  1264. * source mapping location.
  1265. *
  1266. * @param {String} arg[0].url
  1267. * @param {Number} arg[0].line
  1268. * @param {Number?} arg[0].column
  1269. */
  1270. resolveLocation: DebuggerClient.requester({
  1271. type: "resolveLocation",
  1272. location: args(0)
  1273. }),
  1274. };
  1275. eventSource(TabClient.prototype);
  1276. function WorkerClient(aClient, aForm) {
  1277. this.client = aClient;
  1278. this._actor = aForm.from;
  1279. this._isClosed = false;
  1280. this._url = aForm.url;
  1281. this._onClose = this._onClose.bind(this);
  1282. this.addListener("close", this._onClose);
  1283. this.traits = {};
  1284. }
  1285. WorkerClient.prototype = {
  1286. get _transport() {
  1287. return this.client._transport;
  1288. },
  1289. get request() {
  1290. return this.client.request;
  1291. },
  1292. get actor() {
  1293. return this._actor;
  1294. },
  1295. get url() {
  1296. return this._url;
  1297. },
  1298. get isClosed() {
  1299. return this._isClosed;
  1300. },
  1301. detach: DebuggerClient.requester({ type: "detach" }, {
  1302. after: function (aResponse) {
  1303. if (this.thread) {
  1304. this.client.unregisterClient(this.thread);
  1305. }
  1306. this.client.unregisterClient(this);
  1307. return aResponse;
  1308. },
  1309. }),
  1310. attachThread: function (aOptions = {}, aOnResponse = noop) {
  1311. if (this.thread) {
  1312. let response = [{
  1313. type: "connected",
  1314. threadActor: this.thread._actor,
  1315. consoleActor: this.consoleActor,
  1316. }, this.thread];
  1317. DevToolsUtils.executeSoon(() => aOnResponse(response));
  1318. return response;
  1319. }
  1320. // The connect call on server doesn't attach the thread as of version 44.
  1321. return this.request({
  1322. to: this._actor,
  1323. type: "connect",
  1324. options: aOptions,
  1325. }).then(connectReponse => {
  1326. if (connectReponse.error) {
  1327. aOnResponse(connectReponse, null);
  1328. return [connectResponse, null];
  1329. }
  1330. return this.request({
  1331. to: connectReponse.threadActor,
  1332. type: "attach",
  1333. options: aOptions
  1334. }).then(attachResponse => {
  1335. if (attachResponse.error) {
  1336. aOnResponse(attachResponse, null);
  1337. }
  1338. this.thread = new ThreadClient(this, connectReponse.threadActor);
  1339. this.consoleActor = connectReponse.consoleActor;
  1340. this.client.registerClient(this.thread);
  1341. aOnResponse(connectReponse, this.thread);
  1342. return [connectResponse, this.thread];
  1343. });
  1344. }, error => {
  1345. aOnResponse(error, null);
  1346. });
  1347. },
  1348. _onClose: function () {
  1349. this.removeListener("close", this._onClose);
  1350. if (this.thread) {
  1351. this.client.unregisterClient(this.thread);
  1352. }
  1353. this.client.unregisterClient(this);
  1354. this._isClosed = true;
  1355. },
  1356. reconfigure: function () {
  1357. return Promise.resolve();
  1358. },
  1359. events: ["close"]
  1360. };
  1361. eventSource(WorkerClient.prototype);
  1362. function AddonClient(aClient, aActor) {
  1363. this._client = aClient;
  1364. this._actor = aActor;
  1365. this.request = this._client.request;
  1366. this.events = [];
  1367. }
  1368. AddonClient.prototype = {
  1369. get actor() { return this._actor; },
  1370. get _transport() { return this._client._transport; },
  1371. /**
  1372. * Detach the client from the addon actor.
  1373. *
  1374. * @param function aOnResponse
  1375. * Called with the response packet.
  1376. */
  1377. detach: DebuggerClient.requester({
  1378. type: "detach"
  1379. }, {
  1380. after: function (aResponse) {
  1381. if (this._client.activeAddon === this) {
  1382. this._client.activeAddon = null;
  1383. }
  1384. this._client.unregisterClient(this);
  1385. return aResponse;
  1386. },
  1387. })
  1388. };
  1389. /**
  1390. * A RootClient object represents a root actor on the server. Each
  1391. * DebuggerClient keeps a RootClient instance representing the root actor
  1392. * for the initial connection; DebuggerClient's 'listTabs' and
  1393. * 'listChildProcesses' methods forward to that root actor.
  1394. *
  1395. * @param aClient object
  1396. * The client connection to which this actor belongs.
  1397. * @param aGreeting string
  1398. * The greeting packet from the root actor we're to represent.
  1399. *
  1400. * Properties of a RootClient instance:
  1401. *
  1402. * @property actor string
  1403. * The name of this child's root actor.
  1404. * @property applicationType string
  1405. * The application type, as given in the root actor's greeting packet.
  1406. * @property traits object
  1407. * The traits object, as given in the root actor's greeting packet.
  1408. */
  1409. function RootClient(aClient, aGreeting) {
  1410. this._client = aClient;
  1411. this.actor = aGreeting.from;
  1412. this.applicationType = aGreeting.applicationType;
  1413. this.traits = aGreeting.traits;
  1414. }
  1415. exports.RootClient = RootClient;
  1416. RootClient.prototype = {
  1417. constructor: RootClient,
  1418. /**
  1419. * List the open tabs.
  1420. *
  1421. * @param function aOnResponse
  1422. * Called with the response packet.
  1423. */
  1424. listTabs: DebuggerClient.requester({ type: "listTabs" }),
  1425. /**
  1426. * List the installed addons.
  1427. *
  1428. * @param function aOnResponse
  1429. * Called with the response packet.
  1430. */
  1431. listAddons: DebuggerClient.requester({ type: "listAddons" }),
  1432. /**
  1433. * List the registered workers.
  1434. *
  1435. * @param function aOnResponse
  1436. * Called with the response packet.
  1437. */
  1438. listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
  1439. /**
  1440. * List the registered service workers.
  1441. *
  1442. * @param function aOnResponse
  1443. * Called with the response packet.
  1444. */
  1445. listServiceWorkerRegistrations: DebuggerClient.requester({
  1446. type: "listServiceWorkerRegistrations"
  1447. }),
  1448. /**
  1449. * List the running processes.
  1450. *
  1451. * @param function aOnResponse
  1452. * Called with the response packet.
  1453. */
  1454. listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
  1455. /**
  1456. * Fetch the TabActor for the currently selected tab, or for a specific
  1457. * tab given as first parameter.
  1458. *
  1459. * @param [optional] object aFilter
  1460. * A dictionary object with following optional attributes:
  1461. * - outerWindowID: used to match tabs in parent process
  1462. * - tabId: used to match tabs in child processes
  1463. * - tab: a reference to xul:tab element
  1464. * If nothing is specified, returns the actor for the currently
  1465. * selected tab.
  1466. */
  1467. getTab: function (aFilter) {
  1468. let packet = {
  1469. to: this.actor,
  1470. type: "getTab"
  1471. };
  1472. if (aFilter) {
  1473. if (typeof (aFilter.outerWindowID) == "number") {
  1474. packet.outerWindowID = aFilter.outerWindowID;
  1475. } else if (typeof (aFilter.tabId) == "number") {
  1476. packet.tabId = aFilter.tabId;
  1477. } else if ("tab" in aFilter) {
  1478. let browser = aFilter.tab.linkedBrowser;
  1479. if (browser.frameLoader.tabParent) {
  1480. // Tabs in child process
  1481. packet.tabId = browser.frameLoader.tabParent.tabId;
  1482. } else if (browser.outerWindowID) {
  1483. // <xul:browser> tabs in parent process
  1484. packet.outerWindowID = browser.outerWindowID;
  1485. } else {
  1486. // <iframe mozbrowser> tabs in parent process
  1487. let windowUtils = browser.contentWindow
  1488. .QueryInterface(Ci.nsIInterfaceRequestor)
  1489. .getInterface(Ci.nsIDOMWindowUtils);
  1490. packet.outerWindowID = windowUtils.outerWindowID;
  1491. }
  1492. } else {
  1493. // Throw if a filter object have been passed but without
  1494. // any clearly idenfified filter.
  1495. throw new Error("Unsupported argument given to getTab request");
  1496. }
  1497. }
  1498. return this.request(packet);
  1499. },
  1500. /**
  1501. * Description of protocol's actors and methods.
  1502. *
  1503. * @param function aOnResponse
  1504. * Called with the response packet.
  1505. */
  1506. protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
  1507. /*
  1508. * Methods constructed by DebuggerClient.requester require these forwards
  1509. * on their 'this'.
  1510. */
  1511. get _transport() { return this._client._transport; },
  1512. get request() { return this._client.request; }
  1513. };
  1514. /**
  1515. * Creates a thread client for the remote debugging protocol server. This client
  1516. * is a front to the thread actor created in the server side, hiding the
  1517. * protocol details in a traditional JavaScript API.
  1518. *
  1519. * @param aClient DebuggerClient|TabClient
  1520. * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
  1521. * for chrome debuggers).
  1522. * @param aActor string
  1523. * The actor ID for this thread.
  1524. */
  1525. function ThreadClient(aClient, aActor) {
  1526. this._parent = aClient;
  1527. this.client = aClient instanceof DebuggerClient ? aClient : aClient.client;
  1528. this._actor = aActor;
  1529. this._frameCache = [];
  1530. this._scriptCache = {};
  1531. this._pauseGrips = {};
  1532. this._threadGrips = {};
  1533. this.request = this.client.request;
  1534. }
  1535. ThreadClient.prototype = {
  1536. _state: "paused",
  1537. get state() { return this._state; },
  1538. get paused() { return this._state === "paused"; },
  1539. _pauseOnExceptions: false,
  1540. _ignoreCaughtExceptions: false,
  1541. _pauseOnDOMEvents: null,
  1542. _actor: null,
  1543. get actor() { return this._actor; },
  1544. get _transport() { return this.client._transport; },
  1545. _assertPaused: function (aCommand) {
  1546. if (!this.paused) {
  1547. throw Error(aCommand + " command sent while not paused. Currently " + this._state);
  1548. }
  1549. },
  1550. /**
  1551. * Resume a paused thread. If the optional aLimit parameter is present, then
  1552. * the thread will also pause when that limit is reached.
  1553. *
  1554. * @param [optional] object aLimit
  1555. * An object with a type property set to the appropriate limit (next,
  1556. * step, or finish) per the remote debugging protocol specification.
  1557. * Use null to specify no limit.
  1558. * @param function aOnResponse
  1559. * Called with the response packet.
  1560. */
  1561. _doResume: DebuggerClient.requester({
  1562. type: "resume",
  1563. resumeLimit: args(0)
  1564. }, {
  1565. before: function (aPacket) {
  1566. this._assertPaused("resume");
  1567. // Put the client in a tentative "resuming" state so we can prevent
  1568. // further requests that should only be sent in the paused state.
  1569. this._previousState = this._state;
  1570. this._state = "resuming";
  1571. if (this._pauseOnExceptions) {
  1572. aPacket.pauseOnExceptions = this._pauseOnExceptions;
  1573. }
  1574. if (this._ignoreCaughtExceptions) {
  1575. aPacket.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
  1576. }
  1577. if (this._pauseOnDOMEvents) {
  1578. aPacket.pauseOnDOMEvents = this._pauseOnDOMEvents;
  1579. }
  1580. return aPacket;
  1581. },
  1582. after: function (aResponse) {
  1583. if (aResponse.error && this._state == "resuming") {
  1584. // There was an error resuming, update the state to the new one
  1585. // reported by the server, if given (only on wrongState), otherwise
  1586. // reset back to the previous state.
  1587. if (aResponse.state) {
  1588. this._state = ThreadStateTypes[aResponse.state];
  1589. } else {
  1590. this._state = this._previousState;
  1591. }
  1592. }
  1593. delete this._previousState;
  1594. return aResponse;
  1595. },
  1596. }),
  1597. /**
  1598. * Reconfigure the thread actor.
  1599. *
  1600. * @param object aOptions
  1601. * A dictionary object of the new options to use in the thread actor.
  1602. * @param function aOnResponse
  1603. * Called with the response packet.
  1604. */
  1605. reconfigure: DebuggerClient.requester({
  1606. type: "reconfigure",
  1607. options: args(0)
  1608. }),
  1609. /**
  1610. * Resume a paused thread.
  1611. */
  1612. resume: function (aOnResponse) {
  1613. return this._doResume(null, aOnResponse);
  1614. },
  1615. /**
  1616. * Resume then pause without stepping.
  1617. *
  1618. * @param function aOnResponse
  1619. * Called with the response packet.
  1620. */
  1621. resumeThenPause: function (aOnResponse) {
  1622. return this._doResume({ type: "break" }, aOnResponse);
  1623. },
  1624. /**
  1625. * Step over a function call.
  1626. *
  1627. * @param function aOnResponse
  1628. * Called with the response packet.
  1629. */
  1630. stepOver: function (aOnResponse) {
  1631. return this._doResume({ type: "next" }, aOnResponse);
  1632. },
  1633. /**
  1634. * Step into a function call.
  1635. *
  1636. * @param function aOnResponse
  1637. * Called with the response packet.
  1638. */
  1639. stepIn: function (aOnResponse) {
  1640. return this._doResume({ type: "step" }, aOnResponse);
  1641. },
  1642. /**
  1643. * Step out of a function call.
  1644. *
  1645. * @param function aOnResponse
  1646. * Called with the response packet.
  1647. */
  1648. stepOut: function (aOnResponse) {
  1649. return this._doResume({ type: "finish" }, aOnResponse);
  1650. },
  1651. /**
  1652. * Immediately interrupt a running thread.
  1653. *
  1654. * @param function aOnResponse
  1655. * Called with the response packet.
  1656. */
  1657. interrupt: function (aOnResponse) {
  1658. return this._doInterrupt(null, aOnResponse);
  1659. },
  1660. /**
  1661. * Pause execution right before the next JavaScript bytecode is executed.
  1662. *
  1663. * @param function aOnResponse
  1664. * Called with the response packet.
  1665. */
  1666. breakOnNext: function (aOnResponse) {
  1667. return this._doInterrupt("onNext", aOnResponse);
  1668. },
  1669. /**
  1670. * Interrupt a running thread.
  1671. *
  1672. * @param function aOnResponse
  1673. * Called with the response packet.
  1674. */
  1675. _doInterrupt: DebuggerClient.requester({
  1676. type: "interrupt",
  1677. when: args(0)
  1678. }),
  1679. /**
  1680. * Enable or disable pausing when an exception is thrown.
  1681. *
  1682. * @param boolean aFlag
  1683. * Enables pausing if true, disables otherwise.
  1684. * @param function aOnResponse
  1685. * Called with the response packet.
  1686. */
  1687. pauseOnExceptions: function (aPauseOnExceptions,
  1688. aIgnoreCaughtExceptions,
  1689. aOnResponse = noop) {
  1690. this._pauseOnExceptions = aPauseOnExceptions;
  1691. this._ignoreCaughtExceptions = aIgnoreCaughtExceptions;
  1692. // Otherwise send the flag using a standard resume request.
  1693. if (!this.paused) {
  1694. return this.interrupt(aResponse => {
  1695. if (aResponse.error) {
  1696. // Can't continue if pausing failed.
  1697. aOnResponse(aResponse);
  1698. return aResponse;
  1699. }
  1700. return this.resume(aOnResponse);
  1701. });
  1702. }
  1703. aOnResponse();
  1704. return promise.resolve();
  1705. },
  1706. /**
  1707. * Enable pausing when the specified DOM events are triggered. Disabling
  1708. * pausing on an event can be realized by calling this method with the updated
  1709. * array of events that doesn't contain it.
  1710. *
  1711. * @param array|string events
  1712. * An array of strings, representing the DOM event types to pause on,
  1713. * or "*" to pause on all DOM events. Pass an empty array to
  1714. * completely disable pausing on DOM events.
  1715. * @param function onResponse
  1716. * Called with the response packet in a future turn of the event loop.
  1717. */
  1718. pauseOnDOMEvents: function (events, onResponse = noop) {
  1719. this._pauseOnDOMEvents = events;
  1720. // If the debuggee is paused, the value of the array will be communicated in
  1721. // the next resumption. Otherwise we have to force a pause in order to send
  1722. // the array.
  1723. if (this.paused) {
  1724. DevToolsUtils.executeSoon(() => onResponse({}));
  1725. return {};
  1726. }
  1727. return this.interrupt(response => {
  1728. // Can't continue if pausing failed.
  1729. if (response.error) {
  1730. onResponse(response);
  1731. return response;
  1732. }
  1733. return this.resume(onResponse);
  1734. });
  1735. },
  1736. /**
  1737. * Send a clientEvaluate packet to the debuggee. Response
  1738. * will be a resume packet.
  1739. *
  1740. * @param string aFrame
  1741. * The actor ID of the frame where the evaluation should take place.
  1742. * @param string aExpression
  1743. * The expression that will be evaluated in the scope of the frame
  1744. * above.
  1745. * @param function aOnResponse
  1746. * Called with the response packet.
  1747. */
  1748. eval: DebuggerClient.requester({
  1749. type: "clientEvaluate",
  1750. frame: args(0),
  1751. expression: args(1)
  1752. }, {
  1753. before: function (aPacket) {
  1754. this._assertPaused("eval");
  1755. // Put the client in a tentative "resuming" state so we can prevent
  1756. // further requests that should only be sent in the paused state.
  1757. this._state = "resuming";
  1758. return aPacket;
  1759. },
  1760. after: function (aResponse) {
  1761. if (aResponse.error) {
  1762. // There was an error resuming, back to paused state.
  1763. this._state = "paused";
  1764. }
  1765. return aResponse;
  1766. },
  1767. }),
  1768. /**
  1769. * Detach from the thread actor.
  1770. *
  1771. * @param function aOnResponse
  1772. * Called with the response packet.
  1773. */
  1774. detach: DebuggerClient.requester({
  1775. type: "detach"
  1776. }, {
  1777. after: function (aResponse) {
  1778. this.client.unregisterClient(this);
  1779. this._parent.thread = null;
  1780. return aResponse;
  1781. },
  1782. }),
  1783. /**
  1784. * Release multiple thread-lifetime object actors. If any pause-lifetime
  1785. * actors are included in the request, a |notReleasable| error will return,
  1786. * but all the thread-lifetime ones will have been released.
  1787. *
  1788. * @param array actors
  1789. * An array with actor IDs to release.
  1790. */
  1791. releaseMany: DebuggerClient.requester({
  1792. type: "releaseMany",
  1793. actors: args(0),
  1794. }),
  1795. /**
  1796. * Promote multiple pause-lifetime object actors to thread-lifetime ones.
  1797. *
  1798. * @param array actors
  1799. * An array with actor IDs to promote.
  1800. */
  1801. threadGrips: DebuggerClient.requester({
  1802. type: "threadGrips",
  1803. actors: args(0)
  1804. }),
  1805. /**
  1806. * Return the event listeners defined on the page.
  1807. *
  1808. * @param aOnResponse Function
  1809. * Called with the thread's response.
  1810. */
  1811. eventListeners: DebuggerClient.requester({
  1812. type: "eventListeners"
  1813. }),
  1814. /**
  1815. * Request the loaded sources for the current thread.
  1816. *
  1817. * @param aOnResponse Function
  1818. * Called with the thread's response.
  1819. */
  1820. getSources: DebuggerClient.requester({
  1821. type: "sources"
  1822. }),
  1823. /**
  1824. * Clear the thread's source script cache. A scriptscleared event
  1825. * will be sent.
  1826. */
  1827. _clearScripts: function () {
  1828. if (Object.keys(this._scriptCache).length > 0) {
  1829. this._scriptCache = {};
  1830. this.emit("scriptscleared");
  1831. }
  1832. },
  1833. /**
  1834. * Request frames from the callstack for the current thread.
  1835. *
  1836. * @param aStart integer
  1837. * The number of the youngest stack frame to return (the youngest
  1838. * frame is 0).
  1839. * @param aCount integer
  1840. * The maximum number of frames to return, or null to return all
  1841. * frames.
  1842. * @param aOnResponse function
  1843. * Called with the thread's response.
  1844. */
  1845. getFrames: DebuggerClient.requester({
  1846. type: "frames",
  1847. start: args(0),
  1848. count: args(1)
  1849. }),
  1850. /**
  1851. * An array of cached frames. Clients can observe the framesadded and
  1852. * framescleared event to keep up to date on changes to this cache,
  1853. * and can fill it using the fillFrames method.
  1854. */
  1855. get cachedFrames() { return this._frameCache; },
  1856. /**
  1857. * true if there are more stack frames available on the server.
  1858. */
  1859. get moreFrames() {
  1860. return this.paused && (!this._frameCache || this._frameCache.length == 0
  1861. || !this._frameCache[this._frameCache.length - 1].oldest);
  1862. },
  1863. /**
  1864. * Ensure that at least aTotal stack frames have been loaded in the
  1865. * ThreadClient's stack frame cache. A framesadded event will be
  1866. * sent when the stack frame cache is updated.
  1867. *
  1868. * @param aTotal number
  1869. * The minimum number of stack frames to be included.
  1870. * @param aCallback function
  1871. * Optional callback function called when frames have been loaded
  1872. * @returns true if a framesadded notification should be expected.
  1873. */
  1874. fillFrames: function (aTotal, aCallback = noop) {
  1875. this._assertPaused("fillFrames");
  1876. if (this._frameCache.length >= aTotal) {
  1877. return false;
  1878. }
  1879. let numFrames = this._frameCache.length;
  1880. this.getFrames(numFrames, aTotal - numFrames, (aResponse) => {
  1881. if (aResponse.error) {
  1882. aCallback(aResponse);
  1883. return;
  1884. }
  1885. let threadGrips = DevToolsUtils.values(this._threadGrips);
  1886. for (let i in aResponse.frames) {
  1887. let frame = aResponse.frames[i];
  1888. if (!frame.where.source) {
  1889. // Older servers use urls instead, so we need to resolve
  1890. // them to source actors
  1891. for (let grip of threadGrips) {
  1892. if (grip instanceof SourceClient && grip.url === frame.url) {
  1893. frame.where.source = grip._form;
  1894. }
  1895. }
  1896. }
  1897. this._frameCache[frame.depth] = frame;
  1898. }
  1899. // If we got as many frames as we asked for, there might be more
  1900. // frames available.
  1901. this.emit("framesadded");
  1902. aCallback(aResponse);
  1903. });
  1904. return true;
  1905. },
  1906. /**
  1907. * Clear the thread's stack frame cache. A framescleared event
  1908. * will be sent.
  1909. */
  1910. _clearFrames: function () {
  1911. if (this._frameCache.length > 0) {
  1912. this._frameCache = [];
  1913. this.emit("framescleared");
  1914. }
  1915. },
  1916. /**
  1917. * Return a ObjectClient object for the given object grip.
  1918. *
  1919. * @param aGrip object
  1920. * A pause-lifetime object grip returned by the protocol.
  1921. */
  1922. pauseGrip: function (aGrip) {
  1923. if (aGrip.actor in this._pauseGrips) {
  1924. return this._pauseGrips[aGrip.actor];
  1925. }
  1926. let client = new ObjectClient(this.client, aGrip);
  1927. this._pauseGrips[aGrip.actor] = client;
  1928. return client;
  1929. },
  1930. /**
  1931. * Get or create a long string client, checking the grip client cache if it
  1932. * already exists.
  1933. *
  1934. * @param aGrip Object
  1935. * The long string grip returned by the protocol.
  1936. * @param aGripCacheName String
  1937. * The property name of the grip client cache to check for existing
  1938. * clients in.
  1939. */
  1940. _longString: function (aGrip, aGripCacheName) {
  1941. if (aGrip.actor in this[aGripCacheName]) {
  1942. return this[aGripCacheName][aGrip.actor];
  1943. }
  1944. let client = new LongStringClient(this.client, aGrip);
  1945. this[aGripCacheName][aGrip.actor] = client;
  1946. return client;
  1947. },
  1948. /**
  1949. * Return an instance of LongStringClient for the given long string grip that
  1950. * is scoped to the current pause.
  1951. *
  1952. * @param aGrip Object
  1953. * The long string grip returned by the protocol.
  1954. */
  1955. pauseLongString: function (aGrip) {
  1956. return this._longString(aGrip, "_pauseGrips");
  1957. },
  1958. /**
  1959. * Return an instance of LongStringClient for the given long string grip that
  1960. * is scoped to the thread lifetime.
  1961. *
  1962. * @param aGrip Object
  1963. * The long string grip returned by the protocol.
  1964. */
  1965. threadLongString: function (aGrip) {
  1966. return this._longString(aGrip, "_threadGrips");
  1967. },
  1968. /**
  1969. * Clear and invalidate all the grip clients from the given cache.
  1970. *
  1971. * @param aGripCacheName
  1972. * The property name of the grip cache we want to clear.
  1973. */
  1974. _clearObjectClients: function (aGripCacheName) {
  1975. for (let id in this[aGripCacheName]) {
  1976. this[aGripCacheName][id].valid = false;
  1977. }
  1978. this[aGripCacheName] = {};
  1979. },
  1980. /**
  1981. * Invalidate pause-lifetime grip clients and clear the list of current grip
  1982. * clients.
  1983. */
  1984. _clearPauseGrips: function () {
  1985. this._clearObjectClients("_pauseGrips");
  1986. },
  1987. /**
  1988. * Invalidate thread-lifetime grip clients and clear the list of current grip
  1989. * clients.
  1990. */
  1991. _clearThreadGrips: function () {
  1992. this._clearObjectClients("_threadGrips");
  1993. },
  1994. /**
  1995. * Handle thread state change by doing necessary cleanup and notifying all
  1996. * registered listeners.
  1997. */
  1998. _onThreadState: function (aPacket) {
  1999. this._state = ThreadStateTypes[aPacket.type];
  2000. // The debugger UI may not be initialized yet so we want to keep
  2001. // the packet around so it knows what to pause state to display
  2002. // when it's initialized
  2003. this._lastPausePacket = aPacket.type === "resumed" ? null : aPacket;
  2004. this._clearFrames();
  2005. this._clearPauseGrips();
  2006. aPacket.type === ThreadStateTypes.detached && this._clearThreadGrips();
  2007. this.client._eventsEnabled && this.emit(aPacket.type, aPacket);
  2008. },
  2009. getLastPausePacket: function () {
  2010. return this._lastPausePacket;
  2011. },
  2012. /**
  2013. * Return an EnvironmentClient instance for the given environment actor form.
  2014. */
  2015. environment: function (aForm) {
  2016. return new EnvironmentClient(this.client, aForm);
  2017. },
  2018. /**
  2019. * Return an instance of SourceClient for the given source actor form.
  2020. */
  2021. source: function (aForm) {
  2022. if (aForm.actor in this._threadGrips) {
  2023. return this._threadGrips[aForm.actor];
  2024. }
  2025. return this._threadGrips[aForm.actor] = new SourceClient(this, aForm);
  2026. },
  2027. /**
  2028. * Request the prototype and own properties of mutlipleObjects.
  2029. *
  2030. * @param aOnResponse function
  2031. * Called with the request's response.
  2032. * @param actors [string]
  2033. * List of actor ID of the queried objects.
  2034. */
  2035. getPrototypesAndProperties: DebuggerClient.requester({
  2036. type: "prototypesAndProperties",
  2037. actors: args(0)
  2038. }),
  2039. events: ["newSource"]
  2040. };
  2041. eventSource(ThreadClient.prototype);
  2042. /**
  2043. * Creates a tracing profiler client for the remote debugging protocol
  2044. * server. This client is a front to the trace actor created on the
  2045. * server side, hiding the protocol details in a traditional
  2046. * JavaScript API.
  2047. *
  2048. * @param aClient DebuggerClient
  2049. * The debugger client parent.
  2050. * @param aActor string
  2051. * The actor ID for this thread.
  2052. */
  2053. function TraceClient(aClient, aActor) {
  2054. this._client = aClient;
  2055. this._actor = aActor;
  2056. this._activeTraces = new Set();
  2057. this._waitingPackets = new Map();
  2058. this._expectedPacket = 0;
  2059. this.request = this._client.request;
  2060. this.events = [];
  2061. }
  2062. TraceClient.prototype = {
  2063. get actor() { return this._actor; },
  2064. get tracing() { return this._activeTraces.size > 0; },
  2065. get _transport() { return this._client._transport; },
  2066. /**
  2067. * Detach from the trace actor.
  2068. */
  2069. detach: DebuggerClient.requester({
  2070. type: "detach"
  2071. }, {
  2072. after: function (aResponse) {
  2073. this._client.unregisterClient(this);
  2074. return aResponse;
  2075. },
  2076. }),
  2077. /**
  2078. * Start a new trace.
  2079. *
  2080. * @param aTrace [string]
  2081. * An array of trace types to be recorded by the new trace.
  2082. *
  2083. * @param aName string
  2084. * The name of the new trace.
  2085. *
  2086. * @param aOnResponse function
  2087. * Called with the request's response.
  2088. */
  2089. startTrace: DebuggerClient.requester({
  2090. type: "startTrace",
  2091. name: args(1),
  2092. trace: args(0)
  2093. }, {
  2094. after: function (aResponse) {
  2095. if (aResponse.error) {
  2096. return aResponse;
  2097. }
  2098. if (!this.tracing) {
  2099. this._waitingPackets.clear();
  2100. this._expectedPacket = 0;
  2101. }
  2102. this._activeTraces.add(aResponse.name);
  2103. return aResponse;
  2104. },
  2105. }),
  2106. /**
  2107. * End a trace. If a name is provided, stop the named
  2108. * trace. Otherwise, stop the most recently started trace.
  2109. *
  2110. * @param aName string
  2111. * The name of the trace to stop.
  2112. *
  2113. * @param aOnResponse function
  2114. * Called with the request's response.
  2115. */
  2116. stopTrace: DebuggerClient.requester({
  2117. type: "stopTrace",
  2118. name: args(0)
  2119. }, {
  2120. after: function (aResponse) {
  2121. if (aResponse.error) {
  2122. return aResponse;
  2123. }
  2124. this._activeTraces.delete(aResponse.name);
  2125. return aResponse;
  2126. },
  2127. })
  2128. };
  2129. /**
  2130. * Grip clients are used to retrieve information about the relevant object.
  2131. *
  2132. * @param aClient DebuggerClient
  2133. * The debugger client parent.
  2134. * @param aGrip object
  2135. * A pause-lifetime object grip returned by the protocol.
  2136. */
  2137. function ObjectClient(aClient, aGrip)
  2138. {
  2139. this._grip = aGrip;
  2140. this._client = aClient;
  2141. this.request = this._client.request;
  2142. }
  2143. exports.ObjectClient = ObjectClient;
  2144. ObjectClient.prototype = {
  2145. get actor() { return this._grip.actor; },
  2146. get _transport() { return this._client._transport; },
  2147. valid: true,
  2148. get isFrozen() {
  2149. return this._grip.frozen;
  2150. },
  2151. get isSealed() {
  2152. return this._grip.sealed;
  2153. },
  2154. get isExtensible() {
  2155. return this._grip.extensible;
  2156. },
  2157. getDefinitionSite: DebuggerClient.requester({
  2158. type: "definitionSite"
  2159. }, {
  2160. before: function (aPacket) {
  2161. if (this._grip.class != "Function") {
  2162. throw new Error("getDefinitionSite is only valid for function grips.");
  2163. }
  2164. return aPacket;
  2165. }
  2166. }),
  2167. /**
  2168. * Request the names of a function's formal parameters.
  2169. *
  2170. * @param aOnResponse function
  2171. * Called with an object of the form:
  2172. * { parameterNames:[<parameterName>, ...] }
  2173. * where each <parameterName> is the name of a parameter.
  2174. */
  2175. getParameterNames: DebuggerClient.requester({
  2176. type: "parameterNames"
  2177. }, {
  2178. before: function (aPacket) {
  2179. if (this._grip["class"] !== "Function") {
  2180. throw new Error("getParameterNames is only valid for function grips.");
  2181. }
  2182. return aPacket;
  2183. },
  2184. }),
  2185. /**
  2186. * Request the names of the properties defined on the object and not its
  2187. * prototype.
  2188. *
  2189. * @param aOnResponse function Called with the request's response.
  2190. */
  2191. getOwnPropertyNames: DebuggerClient.requester({
  2192. type: "ownPropertyNames"
  2193. }),
  2194. /**
  2195. * Request the prototype and own properties of the object.
  2196. *
  2197. * @param aOnResponse function Called with the request's response.
  2198. */
  2199. getPrototypeAndProperties: DebuggerClient.requester({
  2200. type: "prototypeAndProperties"
  2201. }),
  2202. /**
  2203. * Request a PropertyIteratorClient instance to ease listing
  2204. * properties for this object.
  2205. *
  2206. * @param options Object
  2207. * A dictionary object with various boolean attributes:
  2208. * - ignoreIndexedProperties Boolean
  2209. * If true, filters out Array items.
  2210. * e.g. properties names between `0` and `object.length`.
  2211. * - ignoreNonIndexedProperties Boolean
  2212. * If true, filters out items that aren't array items
  2213. * e.g. properties names that are not a number between `0`
  2214. * and `object.length`.
  2215. * - sort Boolean
  2216. * If true, the iterator will sort the properties by name
  2217. * before dispatching them.
  2218. * @param aOnResponse function Called with the client instance.
  2219. */
  2220. enumProperties: DebuggerClient.requester({
  2221. type: "enumProperties",
  2222. options: args(0)
  2223. }, {
  2224. after: function (aResponse) {
  2225. if (aResponse.iterator) {
  2226. return { iterator: new PropertyIteratorClient(this._client, aResponse.iterator) };
  2227. }
  2228. return aResponse;
  2229. },
  2230. }),
  2231. /**
  2232. * Request a PropertyIteratorClient instance to enumerate entries in a
  2233. * Map/Set-like object.
  2234. *
  2235. * @param aOnResponse function Called with the request's response.
  2236. */
  2237. enumEntries: DebuggerClient.requester({
  2238. type: "enumEntries"
  2239. }, {
  2240. before: function (packet) {
  2241. if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
  2242. throw new Error("enumEntries is only valid for Map/Set-like grips.");
  2243. }
  2244. return packet;
  2245. },
  2246. after: function (response) {
  2247. if (response.iterator) {
  2248. return {
  2249. iterator: new PropertyIteratorClient(this._client, response.iterator)
  2250. };
  2251. }
  2252. return response;
  2253. }
  2254. }),
  2255. /**
  2256. * Request the property descriptor of the object's specified property.
  2257. *
  2258. * @param aName string The name of the requested property.
  2259. * @param aOnResponse function Called with the request's response.
  2260. */
  2261. getProperty: DebuggerClient.requester({
  2262. type: "property",
  2263. name: args(0)
  2264. }),
  2265. /**
  2266. * Request the prototype of the object.
  2267. *
  2268. * @param aOnResponse function Called with the request's response.
  2269. */
  2270. getPrototype: DebuggerClient.requester({
  2271. type: "prototype"
  2272. }),
  2273. /**
  2274. * Request the display string of the object.
  2275. *
  2276. * @param aOnResponse function Called with the request's response.
  2277. */
  2278. getDisplayString: DebuggerClient.requester({
  2279. type: "displayString"
  2280. }),
  2281. /**
  2282. * Request the scope of the object.
  2283. *
  2284. * @param aOnResponse function Called with the request's response.
  2285. */
  2286. getScope: DebuggerClient.requester({
  2287. type: "scope"
  2288. }, {
  2289. before: function (aPacket) {
  2290. if (this._grip.class !== "Function") {
  2291. throw new Error("scope is only valid for function grips.");
  2292. }
  2293. return aPacket;
  2294. },
  2295. }),
  2296. /**
  2297. * Request the promises directly depending on the current promise.
  2298. */
  2299. getDependentPromises: DebuggerClient.requester({
  2300. type: "dependentPromises"
  2301. }, {
  2302. before: function (aPacket) {
  2303. if (this._grip.class !== "Promise") {
  2304. throw new Error("getDependentPromises is only valid for promise " +
  2305. "grips.");
  2306. }
  2307. return aPacket;
  2308. }
  2309. }),
  2310. /**
  2311. * Request the stack to the promise's allocation point.
  2312. */
  2313. getPromiseAllocationStack: DebuggerClient.requester({
  2314. type: "allocationStack"
  2315. }, {
  2316. before: function (aPacket) {
  2317. if (this._grip.class !== "Promise") {
  2318. throw new Error("getAllocationStack is only valid for promise grips.");
  2319. }
  2320. return aPacket;
  2321. }
  2322. }),
  2323. /**
  2324. * Request the stack to the promise's fulfillment point.
  2325. */
  2326. getPromiseFulfillmentStack: DebuggerClient.requester({
  2327. type: "fulfillmentStack"
  2328. }, {
  2329. before: function (packet) {
  2330. if (this._grip.class !== "Promise") {
  2331. throw new Error("getPromiseFulfillmentStack is only valid for " +
  2332. "promise grips.");
  2333. }
  2334. return packet;
  2335. }
  2336. }),
  2337. /**
  2338. * Request the stack to the promise's rejection point.
  2339. */
  2340. getPromiseRejectionStack: DebuggerClient.requester({
  2341. type: "rejectionStack"
  2342. }, {
  2343. before: function (packet) {
  2344. if (this._grip.class !== "Promise") {
  2345. throw new Error("getPromiseRejectionStack is only valid for " +
  2346. "promise grips.");
  2347. }
  2348. return packet;
  2349. }
  2350. })
  2351. };
  2352. /**
  2353. * A PropertyIteratorClient provides a way to access to property names and
  2354. * values of an object efficiently, slice by slice.
  2355. * Note that the properties can be sorted in the backend,
  2356. * this is controled while creating the PropertyIteratorClient
  2357. * from ObjectClient.enumProperties.
  2358. *
  2359. * @param aClient DebuggerClient
  2360. * The debugger client parent.
  2361. * @param aGrip Object
  2362. * A PropertyIteratorActor grip returned by the protocol via
  2363. * TabActor.enumProperties request.
  2364. */
  2365. function PropertyIteratorClient(aClient, aGrip) {
  2366. this._grip = aGrip;
  2367. this._client = aClient;
  2368. this.request = this._client.request;
  2369. }
  2370. PropertyIteratorClient.prototype = {
  2371. get actor() { return this._grip.actor; },
  2372. /**
  2373. * Get the total number of properties available in the iterator.
  2374. */
  2375. get count() { return this._grip.count; },
  2376. /**
  2377. * Get one or more property names that correspond to the positions in the
  2378. * indexes parameter.
  2379. *
  2380. * @param indexes Array
  2381. * An array of property indexes.
  2382. * @param aCallback Function
  2383. * The function called when we receive the property names.
  2384. */
  2385. names: DebuggerClient.requester({
  2386. type: "names",
  2387. indexes: args(0)
  2388. }, {}),
  2389. /**
  2390. * Get a set of following property value(s).
  2391. *
  2392. * @param start Number
  2393. * The index of the first property to fetch.
  2394. * @param count Number
  2395. * The number of properties to fetch.
  2396. * @param aCallback Function
  2397. * The function called when we receive the property values.
  2398. */
  2399. slice: DebuggerClient.requester({
  2400. type: "slice",
  2401. start: args(0),
  2402. count: args(1)
  2403. }, {}),
  2404. /**
  2405. * Get all the property values.
  2406. *
  2407. * @param aCallback Function
  2408. * The function called when we receive the property values.
  2409. */
  2410. all: DebuggerClient.requester({
  2411. type: "all"
  2412. }, {}),
  2413. };
  2414. /**
  2415. * A LongStringClient provides a way to access "very long" strings from the
  2416. * debugger server.
  2417. *
  2418. * @param aClient DebuggerClient
  2419. * The debugger client parent.
  2420. * @param aGrip Object
  2421. * A pause-lifetime long string grip returned by the protocol.
  2422. */
  2423. function LongStringClient(aClient, aGrip) {
  2424. this._grip = aGrip;
  2425. this._client = aClient;
  2426. this.request = this._client.request;
  2427. }
  2428. exports.LongStringClient = LongStringClient;
  2429. LongStringClient.prototype = {
  2430. get actor() { return this._grip.actor; },
  2431. get length() { return this._grip.length; },
  2432. get initial() { return this._grip.initial; },
  2433. get _transport() { return this._client._transport; },
  2434. valid: true,
  2435. /**
  2436. * Get the substring of this LongString from aStart to aEnd.
  2437. *
  2438. * @param aStart Number
  2439. * The starting index.
  2440. * @param aEnd Number
  2441. * The ending index.
  2442. * @param aCallback Function
  2443. * The function called when we receive the substring.
  2444. */
  2445. substring: DebuggerClient.requester({
  2446. type: "substring",
  2447. start: args(0),
  2448. end: args(1)
  2449. }),
  2450. };
  2451. /**
  2452. * A SourceClient provides a way to access the source text of a script.
  2453. *
  2454. * @param aClient ThreadClient
  2455. * The thread client parent.
  2456. * @param aForm Object
  2457. * The form sent across the remote debugging protocol.
  2458. */
  2459. function SourceClient(aClient, aForm) {
  2460. this._form = aForm;
  2461. this._isBlackBoxed = aForm.isBlackBoxed;
  2462. this._isPrettyPrinted = aForm.isPrettyPrinted;
  2463. this._activeThread = aClient;
  2464. this._client = aClient.client;
  2465. }
  2466. SourceClient.prototype = {
  2467. get _transport() {
  2468. return this._client._transport;
  2469. },
  2470. get isBlackBoxed() {
  2471. return this._isBlackBoxed;
  2472. },
  2473. get isPrettyPrinted() {
  2474. return this._isPrettyPrinted;
  2475. },
  2476. get actor() {
  2477. return this._form.actor;
  2478. },
  2479. get request() {
  2480. return this._client.request;
  2481. },
  2482. get url() {
  2483. return this._form.url;
  2484. },
  2485. /**
  2486. * Black box this SourceClient's source.
  2487. *
  2488. * @param aCallback Function
  2489. * The callback function called when we receive the response from the server.
  2490. */
  2491. blackBox: DebuggerClient.requester({
  2492. type: "blackbox"
  2493. }, {
  2494. after: function (aResponse) {
  2495. if (!aResponse.error) {
  2496. this._isBlackBoxed = true;
  2497. if (this._activeThread) {
  2498. this._activeThread.emit("blackboxchange", this);
  2499. }
  2500. }
  2501. return aResponse;
  2502. }
  2503. }),
  2504. /**
  2505. * Un-black box this SourceClient's source.
  2506. *
  2507. * @param aCallback Function
  2508. * The callback function called when we receive the response from the server.
  2509. */
  2510. unblackBox: DebuggerClient.requester({
  2511. type: "unblackbox"
  2512. }, {
  2513. after: function (aResponse) {
  2514. if (!aResponse.error) {
  2515. this._isBlackBoxed = false;
  2516. if (this._activeThread) {
  2517. this._activeThread.emit("blackboxchange", this);
  2518. }
  2519. }
  2520. return aResponse;
  2521. }
  2522. }),
  2523. /**
  2524. * Get Executable Lines from a source
  2525. *
  2526. * @param aCallback Function
  2527. * The callback function called when we receive the response from the server.
  2528. */
  2529. getExecutableLines: function (cb = noop) {
  2530. let packet = {
  2531. to: this._form.actor,
  2532. type: "getExecutableLines"
  2533. };
  2534. return this._client.request(packet).then(res => {
  2535. cb(res.lines);
  2536. return res.lines;
  2537. });
  2538. },
  2539. /**
  2540. * Get a long string grip for this SourceClient's source.
  2541. */
  2542. source: function (aCallback = noop) {
  2543. let packet = {
  2544. to: this._form.actor,
  2545. type: "source"
  2546. };
  2547. return this._client.request(packet).then(aResponse => {
  2548. return this._onSourceResponse(aResponse, aCallback);
  2549. });
  2550. },
  2551. /**
  2552. * Pretty print this source's text.
  2553. */
  2554. prettyPrint: function (aIndent, aCallback = noop) {
  2555. const packet = {
  2556. to: this._form.actor,
  2557. type: "prettyPrint",
  2558. indent: aIndent
  2559. };
  2560. return this._client.request(packet).then(aResponse => {
  2561. if (!aResponse.error) {
  2562. this._isPrettyPrinted = true;
  2563. this._activeThread._clearFrames();
  2564. this._activeThread.emit("prettyprintchange", this);
  2565. }
  2566. return this._onSourceResponse(aResponse, aCallback);
  2567. });
  2568. },
  2569. /**
  2570. * Stop pretty printing this source's text.
  2571. */
  2572. disablePrettyPrint: function (aCallback = noop) {
  2573. const packet = {
  2574. to: this._form.actor,
  2575. type: "disablePrettyPrint"
  2576. };
  2577. return this._client.request(packet).then(aResponse => {
  2578. if (!aResponse.error) {
  2579. this._isPrettyPrinted = false;
  2580. this._activeThread._clearFrames();
  2581. this._activeThread.emit("prettyprintchange", this);
  2582. }
  2583. return this._onSourceResponse(aResponse, aCallback);
  2584. });
  2585. },
  2586. _onSourceResponse: function (aResponse, aCallback) {
  2587. if (aResponse.error) {
  2588. aCallback(aResponse);
  2589. return aResponse;
  2590. }
  2591. if (typeof aResponse.source === "string") {
  2592. aCallback(aResponse);
  2593. return aResponse;
  2594. }
  2595. let { contentType, source } = aResponse;
  2596. let longString = this._activeThread.threadLongString(source);
  2597. return longString.substring(0, longString.length).then(function (aResponse) {
  2598. if (aResponse.error) {
  2599. aCallback(aResponse);
  2600. return aReponse;
  2601. }
  2602. let response = {
  2603. source: aResponse.substring,
  2604. contentType: contentType
  2605. };
  2606. aCallback(response);
  2607. return response;
  2608. });
  2609. },
  2610. /**
  2611. * Request to set a breakpoint in the specified location.
  2612. *
  2613. * @param object aLocation
  2614. * The location and condition of the breakpoint in
  2615. * the form of { line[, column, condition] }.
  2616. * @param function aOnResponse
  2617. * Called with the thread's response.
  2618. */
  2619. setBreakpoint: function ({ line, column, condition, noSliding }, aOnResponse = noop) {
  2620. // A helper function that sets the breakpoint.
  2621. let doSetBreakpoint = aCallback => {
  2622. let root = this._client.mainRoot;
  2623. let location = {
  2624. line: line,
  2625. column: column
  2626. };
  2627. let packet = {
  2628. to: this.actor,
  2629. type: "setBreakpoint",
  2630. location: location,
  2631. condition: condition,
  2632. noSliding: noSliding
  2633. };
  2634. // Backwards compatibility: send the breakpoint request to the
  2635. // thread if the server doesn't support Debugger.Source actors.
  2636. if (!root.traits.debuggerSourceActors) {
  2637. packet.to = this._activeThread.actor;
  2638. packet.location.url = this.url;
  2639. }
  2640. return this._client.request(packet).then(aResponse => {
  2641. // Ignoring errors, since the user may be setting a breakpoint in a
  2642. // dead script that will reappear on a page reload.
  2643. let bpClient;
  2644. if (aResponse.actor) {
  2645. bpClient = new BreakpointClient(
  2646. this._client,
  2647. this,
  2648. aResponse.actor,
  2649. location,
  2650. root.traits.conditionalBreakpoints ? condition : undefined
  2651. );
  2652. }
  2653. aOnResponse(aResponse, bpClient);
  2654. if (aCallback) {
  2655. aCallback();
  2656. }
  2657. return [aResponse, bpClient];
  2658. });
  2659. };
  2660. // If the debuggee is paused, just set the breakpoint.
  2661. if (this._activeThread.paused) {
  2662. return doSetBreakpoint();
  2663. }
  2664. // Otherwise, force a pause in order to set the breakpoint.
  2665. return this._activeThread.interrupt().then(aResponse => {
  2666. if (aResponse.error) {
  2667. // Can't set the breakpoint if pausing failed.
  2668. aOnResponse(aResponse);
  2669. return aResponse;
  2670. }
  2671. const { type, why } = aResponse;
  2672. const cleanUp = type == "paused" && why.type == "interrupted"
  2673. ? () => this._activeThread.resume()
  2674. : noop;
  2675. return doSetBreakpoint(cleanUp);
  2676. });
  2677. }
  2678. };
  2679. /**
  2680. * Breakpoint clients are used to remove breakpoints that are no longer used.
  2681. *
  2682. * @param aClient DebuggerClient
  2683. * The debugger client parent.
  2684. * @param aSourceClient SourceClient
  2685. * The source where this breakpoint exists
  2686. * @param aActor string
  2687. * The actor ID for this breakpoint.
  2688. * @param aLocation object
  2689. * The location of the breakpoint. This is an object with two properties:
  2690. * url and line.
  2691. * @param aCondition string
  2692. * The conditional expression of the breakpoint
  2693. */
  2694. function BreakpointClient(aClient, aSourceClient, aActor, aLocation, aCondition) {
  2695. this._client = aClient;
  2696. this._actor = aActor;
  2697. this.location = aLocation;
  2698. this.location.actor = aSourceClient.actor;
  2699. this.location.url = aSourceClient.url;
  2700. this.source = aSourceClient;
  2701. this.request = this._client.request;
  2702. // The condition property should only exist if it's a truthy value
  2703. if (aCondition) {
  2704. this.condition = aCondition;
  2705. }
  2706. }
  2707. BreakpointClient.prototype = {
  2708. _actor: null,
  2709. get actor() { return this._actor; },
  2710. get _transport() { return this._client._transport; },
  2711. /**
  2712. * Remove the breakpoint from the server.
  2713. */
  2714. remove: DebuggerClient.requester({
  2715. type: "delete"
  2716. }),
  2717. /**
  2718. * Determines if this breakpoint has a condition
  2719. */
  2720. hasCondition: function () {
  2721. let root = this._client.mainRoot;
  2722. // XXX bug 990137: We will remove support for client-side handling of
  2723. // conditional breakpoints
  2724. if (root.traits.conditionalBreakpoints) {
  2725. return "condition" in this;
  2726. } else {
  2727. return "conditionalExpression" in this;
  2728. }
  2729. },
  2730. /**
  2731. * Get the condition of this breakpoint. Currently we have to
  2732. * support locally emulated conditional breakpoints until the
  2733. * debugger servers are updated (see bug 990137). We used a
  2734. * different property when moving it server-side to ensure that we
  2735. * are testing the right code.
  2736. */
  2737. getCondition: function () {
  2738. let root = this._client.mainRoot;
  2739. if (root.traits.conditionalBreakpoints) {
  2740. return this.condition;
  2741. } else {
  2742. return this.conditionalExpression;
  2743. }
  2744. },
  2745. /**
  2746. * Set the condition of this breakpoint
  2747. */
  2748. setCondition: function (gThreadClient, aCondition) {
  2749. let root = this._client.mainRoot;
  2750. let deferred = promise.defer();
  2751. if (root.traits.conditionalBreakpoints) {
  2752. let info = {
  2753. line: this.location.line,
  2754. column: this.location.column,
  2755. condition: aCondition
  2756. };
  2757. // Remove the current breakpoint and add a new one with the
  2758. // condition.
  2759. this.remove(aResponse => {
  2760. if (aResponse && aResponse.error) {
  2761. deferred.reject(aResponse);
  2762. return;
  2763. }
  2764. this.source.setBreakpoint(info, (aResponse, aNewBreakpoint) => {
  2765. if (aResponse && aResponse.error) {
  2766. deferred.reject(aResponse);
  2767. } else {
  2768. deferred.resolve(aNewBreakpoint);
  2769. }
  2770. });
  2771. });
  2772. } else {
  2773. // The property shouldn't even exist if the condition is blank
  2774. if (aCondition === "") {
  2775. delete this.conditionalExpression;
  2776. }
  2777. else {
  2778. this.conditionalExpression = aCondition;
  2779. }
  2780. deferred.resolve(this);
  2781. }
  2782. return deferred.promise;
  2783. }
  2784. };
  2785. eventSource(BreakpointClient.prototype);
  2786. /**
  2787. * Environment clients are used to manipulate the lexical environment actors.
  2788. *
  2789. * @param aClient DebuggerClient
  2790. * The debugger client parent.
  2791. * @param aForm Object
  2792. * The form sent across the remote debugging protocol.
  2793. */
  2794. function EnvironmentClient(aClient, aForm) {
  2795. this._client = aClient;
  2796. this._form = aForm;
  2797. this.request = this._client.request;
  2798. }
  2799. exports.EnvironmentClient = EnvironmentClient;
  2800. EnvironmentClient.prototype = {
  2801. get actor() {
  2802. return this._form.actor;
  2803. },
  2804. get _transport() { return this._client._transport; },
  2805. /**
  2806. * Fetches the bindings introduced by this lexical environment.
  2807. */
  2808. getBindings: DebuggerClient.requester({
  2809. type: "bindings"
  2810. }),
  2811. /**
  2812. * Changes the value of the identifier whose name is name (a string) to that
  2813. * represented by value (a grip).
  2814. */
  2815. assign: DebuggerClient.requester({
  2816. type: "assign",
  2817. name: args(0),
  2818. value: args(1)
  2819. })
  2820. };
  2821. eventSource(EnvironmentClient.prototype);