123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123 |
- /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- "use strict";
- const { Ci, Cu } = require("chrome");
- const Services = require("Services");
- const DevToolsUtils = require("devtools/shared/DevToolsUtils");
- const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
- const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
- loader.lazyRequireGetter(this, "events", "sdk/event/core");
- loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
- loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
- loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
- const noop = () => {};
- /**
- * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
- *
- * Add simple event notification to a prototype object. Any object that has
- * some use for event notifications or the observer pattern in general can be
- * augmented with the necessary facilities by passing its prototype to this
- * function.
- *
- * @param aProto object
- * The prototype object that will be modified.
- */
- function eventSource(aProto) {
- /**
- * Add a listener to the event source for a given event.
- *
- * @param aName string
- * The event to listen for.
- * @param aListener function
- * Called when the event is fired. If the same listener
- * is added more than once, it will be called once per
- * addListener call.
- */
- aProto.addListener = function (aName, aListener) {
- if (typeof aListener != "function") {
- throw TypeError("Listeners must be functions.");
- }
- if (!this._listeners) {
- this._listeners = {};
- }
- this._getListeners(aName).push(aListener);
- };
- /**
- * Add a listener to the event source for a given event. The
- * listener will be removed after it is called for the first time.
- *
- * @param aName string
- * The event to listen for.
- * @param aListener function
- * Called when the event is fired.
- */
- aProto.addOneTimeListener = function (aName, aListener) {
- let l = (...args) => {
- this.removeListener(aName, l);
- aListener.apply(null, args);
- };
- this.addListener(aName, l);
- };
- /**
- * Remove a listener from the event source previously added with
- * addListener().
- *
- * @param aName string
- * The event name used during addListener to add the listener.
- * @param aListener function
- * The callback to remove. If addListener was called multiple
- * times, all instances will be removed.
- */
- aProto.removeListener = function (aName, aListener) {
- if (!this._listeners || (aListener && !this._listeners[aName])) {
- return;
- }
- if (!aListener) {
- this._listeners[aName] = [];
- }
- else {
- this._listeners[aName] =
- this._listeners[aName].filter(function (l) { return l != aListener; });
- }
- };
- /**
- * Returns the listeners for the specified event name. If none are defined it
- * initializes an empty list and returns that.
- *
- * @param aName string
- * The event name.
- */
- aProto._getListeners = function (aName) {
- if (aName in this._listeners) {
- return this._listeners[aName];
- }
- this._listeners[aName] = [];
- return this._listeners[aName];
- };
- /**
- * Notify listeners of an event.
- *
- * @param aName string
- * The event to fire.
- * @param arguments
- * All arguments will be passed along to the listeners,
- * including the name argument.
- */
- aProto.emit = function () {
- if (!this._listeners) {
- return;
- }
- let name = arguments[0];
- let listeners = this._getListeners(name).slice(0);
- for (let listener of listeners) {
- try {
- listener.apply(null, arguments);
- } catch (e) {
- // Prevent a bad listener from interfering with the others.
- DevToolsUtils.reportException("notify event '" + name + "'", e);
- }
- }
- };
- }
- /**
- * Set of protocol messages that affect thread state, and the
- * state the actor is in after each message.
- */
- const ThreadStateTypes = {
- "paused": "paused",
- "resumed": "attached",
- "detached": "detached",
- "running": "attached"
- };
- /**
- * Set of protocol messages that are sent by the server without a prior request
- * by the client.
- */
- const UnsolicitedNotifications = {
- "consoleAPICall": "consoleAPICall",
- "eventNotification": "eventNotification",
- "fileActivity": "fileActivity",
- "lastPrivateContextExited": "lastPrivateContextExited",
- "logMessage": "logMessage",
- "networkEvent": "networkEvent",
- "networkEventUpdate": "networkEventUpdate",
- "newGlobal": "newGlobal",
- "newScript": "newScript",
- "tabDetached": "tabDetached",
- "tabListChanged": "tabListChanged",
- "reflowActivity": "reflowActivity",
- "addonListChanged": "addonListChanged",
- "workerListChanged": "workerListChanged",
- "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
- "tabNavigated": "tabNavigated",
- "frameUpdate": "frameUpdate",
- "pageError": "pageError",
- "documentLoad": "documentLoad",
- "enteredFrame": "enteredFrame",
- "exitedFrame": "exitedFrame",
- "appOpen": "appOpen",
- "appClose": "appClose",
- "appInstall": "appInstall",
- "appUninstall": "appUninstall",
- "evaluationResult": "evaluationResult",
- "newSource": "newSource",
- "updatedSource": "updatedSource",
- };
- /**
- * Set of pause types that are sent by the server and not as an immediate
- * response to a client request.
- */
- const UnsolicitedPauses = {
- "resumeLimit": "resumeLimit",
- "debuggerStatement": "debuggerStatement",
- "breakpoint": "breakpoint",
- "DOMEvent": "DOMEvent",
- "watchpoint": "watchpoint",
- "exception": "exception"
- };
- /**
- * Creates a client for the remote debugging protocol server. This client
- * provides the means to communicate with the server and exchange the messages
- * required by the protocol in a traditional JavaScript API.
- */
- const DebuggerClient = exports.DebuggerClient = function (aTransport)
- {
- this._transport = aTransport;
- this._transport.hooks = this;
- // Map actor ID to client instance for each actor type.
- this._clients = new Map();
- this._pendingRequests = new Map();
- this._activeRequests = new Map();
- this._eventsEnabled = true;
- this.traits = {};
- this.request = this.request.bind(this);
- this.localTransport = this._transport.onOutputStreamReady === undefined;
- /*
- * As the first thing on the connection, expect a greeting packet from
- * the connection's root actor.
- */
- this.mainRoot = null;
- this.expectReply("root", (aPacket) => {
- this.mainRoot = new RootClient(this, aPacket);
- this.emit("connected", aPacket.applicationType, aPacket.traits);
- });
- };
- /**
- * A declarative helper for defining methods that send requests to the server.
- *
- * @param aPacketSkeleton
- * The form of the packet to send. Can specify fields to be filled from
- * the parameters by using the |args| function.
- * @param before
- * The function to call before sending the packet. Is passed the packet,
- * and the return value is used as the new packet. The |this| context is
- * the instance of the client object we are defining a method for.
- * @param after
- * The function to call after the response is received. It is passed the
- * response, and the return value is considered the new response that
- * will be passed to the callback. The |this| context is the instance of
- * the client object we are defining a method for.
- * @return Request
- * The `Request` object that is a Promise object and resolves once
- * we receive the response. (See request method for more details)
- */
- DebuggerClient.requester = function (aPacketSkeleton, config = {}) {
- let { before, after } = config;
- return DevToolsUtils.makeInfallible(function (...args) {
- let outgoingPacket = {
- to: aPacketSkeleton.to || this.actor
- };
- let maxPosition = -1;
- for (let k of Object.keys(aPacketSkeleton)) {
- if (aPacketSkeleton[k] instanceof DebuggerClient.Argument) {
- let { position } = aPacketSkeleton[k];
- outgoingPacket[k] = aPacketSkeleton[k].getArgument(args);
- maxPosition = Math.max(position, maxPosition);
- } else {
- outgoingPacket[k] = aPacketSkeleton[k];
- }
- }
- if (before) {
- outgoingPacket = before.call(this, outgoingPacket);
- }
- return this.request(outgoingPacket, DevToolsUtils.makeInfallible((aResponse) => {
- if (after) {
- let { from } = aResponse;
- aResponse = after.call(this, aResponse);
- if (!aResponse.from) {
- aResponse.from = from;
- }
- }
- // The callback is always the last parameter.
- let thisCallback = args[maxPosition + 1];
- if (thisCallback) {
- thisCallback(aResponse);
- }
- }, "DebuggerClient.requester request callback"));
- }, "DebuggerClient.requester");
- };
- function args(aPos) {
- return new DebuggerClient.Argument(aPos);
- }
- DebuggerClient.Argument = function (aPosition) {
- this.position = aPosition;
- };
- DebuggerClient.Argument.prototype.getArgument = function (aParams) {
- if (!(this.position in aParams)) {
- throw new Error("Bad index into params: " + this.position);
- }
- return aParams[this.position];
- };
- // Expose these to save callers the trouble of importing DebuggerSocket
- DebuggerClient.socketConnect = function (options) {
- // Defined here instead of just copying the function to allow lazy-load
- return DebuggerSocket.connect(options);
- };
- DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
- return Authentication.Authenticators;
- });
- DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
- return Authentication.AuthenticationResult;
- });
- DebuggerClient.prototype = {
- /**
- * Connect to the server and start exchanging protocol messages.
- *
- * @param aOnConnected function
- * If specified, will be called when the greeting packet is
- * received from the debugging server.
- *
- * @return Promise
- * Resolves once connected with an array whose first element
- * is the application type, by default "browser", and the second
- * element is the traits object (help figure out the features
- * and behaviors of the server we connect to. See RootActor).
- */
- connect: function (aOnConnected) {
- let deferred = promise.defer();
- this.emit("connect");
- // Also emit the event on the |DebuggerClient| object (not on the instance),
- // so it's possible to track all instances.
- events.emit(DebuggerClient, "connect", this);
- this.addOneTimeListener("connected", (aName, aApplicationType, aTraits) => {
- this.traits = aTraits;
- if (aOnConnected) {
- aOnConnected(aApplicationType, aTraits);
- }
- deferred.resolve([aApplicationType, aTraits]);
- });
- this._transport.ready();
- return deferred.promise;
- },
- /**
- * Shut down communication with the debugging server.
- *
- * @param aOnClosed function
- * If specified, will be called when the debugging connection
- * has been closed. This parameter is deprecated - please use
- * the returned Promise.
- * @return Promise
- * Resolves after the underlying transport is closed.
- */
- close: function (aOnClosed) {
- let deferred = promise.defer();
- if (aOnClosed) {
- deferred.promise.then(aOnClosed);
- }
- // Disable detach event notifications, because event handlers will be in a
- // cleared scope by the time they run.
- this._eventsEnabled = false;
- let cleanup = () => {
- this._transport.close();
- this._transport = null;
- };
- // If the connection is already closed,
- // there is no need to detach client
- // as we won't be able to send any message.
- if (this._closed) {
- cleanup();
- deferred.resolve();
- return deferred.promise;
- }
- this.addOneTimeListener("closed", deferred.resolve);
- // Call each client's `detach` method by calling
- // lastly registered ones first to give a chance
- // to detach child clients first.
- let clients = [...this._clients.values()];
- this._clients.clear();
- const detachClients = () => {
- let client = clients.pop();
- if (!client) {
- // All clients detached.
- cleanup();
- return;
- }
- if (client.detach) {
- client.detach(detachClients);
- return;
- }
- detachClients();
- };
- detachClients();
- return deferred.promise;
- },
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listTabs()'.
- */
- listTabs: function (aOnResponse) { return this.mainRoot.listTabs(aOnResponse); },
- /*
- * This function exists only to preserve DebuggerClient's interface;
- * new code should say 'client.mainRoot.listAddons()'.
- */
- listAddons: function (aOnResponse) { return this.mainRoot.listAddons(aOnResponse); },
- getTab: function (aFilter) { return this.mainRoot.getTab(aFilter); },
- /**
- * Attach to a tab actor.
- *
- * @param string aTabActor
- * The actor ID for the tab to attach.
- * @param function aOnResponse
- * Called with the response packet and a TabClient
- * (which will be undefined on error).
- */
- attachTab: function (aTabActor, aOnResponse = noop) {
- if (this._clients.has(aTabActor)) {
- let cachedTab = this._clients.get(aTabActor);
- let cachedResponse = {
- cacheDisabled: cachedTab.cacheDisabled,
- javascriptEnabled: cachedTab.javascriptEnabled,
- traits: cachedTab.traits,
- };
- DevToolsUtils.executeSoon(() => aOnResponse(cachedResponse, cachedTab));
- return promise.resolve([cachedResponse, cachedTab]);
- }
- let packet = {
- to: aTabActor,
- type: "attach"
- };
- return this.request(packet).then(aResponse => {
- let tabClient;
- if (!aResponse.error) {
- tabClient = new TabClient(this, aResponse);
- this.registerClient(tabClient);
- }
- aOnResponse(aResponse, tabClient);
- return [aResponse, tabClient];
- });
- },
- attachWorker: function DC_attachWorker(aWorkerActor, aOnResponse = noop) {
- let workerClient = this._clients.get(aWorkerActor);
- if (workerClient !== undefined) {
- let response = {
- from: workerClient.actor,
- type: "attached",
- url: workerClient.url
- };
- DevToolsUtils.executeSoon(() => aOnResponse(response, workerClient));
- return promise.resolve([response, workerClient]);
- }
- return this.request({ to: aWorkerActor, type: "attach" }).then(aResponse => {
- if (aResponse.error) {
- aOnResponse(aResponse, null);
- return [aResponse, null];
- }
- let workerClient = new WorkerClient(this, aResponse);
- this.registerClient(workerClient);
- aOnResponse(aResponse, workerClient);
- return [aResponse, workerClient];
- });
- },
- /**
- * Attach to an addon actor.
- *
- * @param string aAddonActor
- * The actor ID for the addon to attach.
- * @param function aOnResponse
- * Called with the response packet and a AddonClient
- * (which will be undefined on error).
- */
- attachAddon: function DC_attachAddon(aAddonActor, aOnResponse = noop) {
- let packet = {
- to: aAddonActor,
- type: "attach"
- };
- return this.request(packet).then(aResponse => {
- let addonClient;
- if (!aResponse.error) {
- addonClient = new AddonClient(this, aAddonActor);
- this.registerClient(addonClient);
- this.activeAddon = addonClient;
- }
- aOnResponse(aResponse, addonClient);
- return [aResponse, addonClient];
- });
- },
- /**
- * Attach to a Web Console actor.
- *
- * @param string aConsoleActor
- * The ID for the console actor to attach to.
- * @param array aListeners
- * The console listeners you want to start.
- * @param function aOnResponse
- * Called with the response packet and a WebConsoleClient
- * instance (which will be undefined on error).
- */
- attachConsole:
- function (aConsoleActor, aListeners, aOnResponse = noop) {
- let packet = {
- to: aConsoleActor,
- type: "startListeners",
- listeners: aListeners,
- };
- return this.request(packet).then(aResponse => {
- let consoleClient;
- if (!aResponse.error) {
- if (this._clients.has(aConsoleActor)) {
- consoleClient = this._clients.get(aConsoleActor);
- } else {
- consoleClient = new WebConsoleClient(this, aResponse);
- this.registerClient(consoleClient);
- }
- }
- aOnResponse(aResponse, consoleClient);
- return [aResponse, consoleClient];
- });
- },
- /**
- * Attach to a global-scoped thread actor for chrome debugging.
- *
- * @param string aThreadActor
- * The actor ID for the thread to attach.
- * @param function aOnResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- * @param object aOptions
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- */
- attachThread: function (aThreadActor, aOnResponse = noop, aOptions = {}) {
- if (this._clients.has(aThreadActor)) {
- let client = this._clients.get(aThreadActor);
- DevToolsUtils.executeSoon(() => aOnResponse({}, client));
- return promise.resolve([{}, client]);
- }
- let packet = {
- to: aThreadActor,
- type: "attach",
- options: aOptions
- };
- return this.request(packet).then(aResponse => {
- if (!aResponse.error) {
- var threadClient = new ThreadClient(this, aThreadActor);
- this.registerClient(threadClient);
- }
- aOnResponse(aResponse, threadClient);
- return [aResponse, threadClient];
- });
- },
- /**
- * Attach to a trace actor.
- *
- * @param string aTraceActor
- * The actor ID for the tracer to attach.
- * @param function aOnResponse
- * Called with the response packet and a TraceClient
- * (which will be undefined on error).
- */
- attachTracer: function (aTraceActor, aOnResponse = noop) {
- if (this._clients.has(aTraceActor)) {
- let client = this._clients.get(aTraceActor);
- DevToolsUtils.executeSoon(() => aOnResponse({}, client));
- return promise.resolve([{}, client]);
- }
- let packet = {
- to: aTraceActor,
- type: "attach"
- };
- return this.request(packet).then(aResponse => {
- if (!aResponse.error) {
- var traceClient = new TraceClient(this, aTraceActor);
- this.registerClient(traceClient);
- }
- aOnResponse(aResponse, traceClient);
- return [aResponse, traceClient];
- });
- },
- /**
- * Fetch the ChromeActor for the main process or ChildProcessActor for a
- * a given child process ID.
- *
- * @param number aId
- * The ID for the process to attach (returned by `listProcesses`).
- * Connected to the main process if omitted, or is 0.
- */
- getProcess: function (aId) {
- let packet = {
- to: "root",
- type: "getProcess"
- };
- if (typeof (aId) == "number") {
- packet.id = aId;
- }
- return this.request(packet);
- },
- /**
- * Release an object actor.
- *
- * @param string aActor
- * The actor ID to send the request to.
- * @param aOnResponse function
- * If specified, will be called with the response packet when
- * debugging server responds.
- */
- release: DebuggerClient.requester({
- to: args(0),
- type: "release"
- }),
- /**
- * Send a request to the debugging server.
- *
- * @param aRequest object
- * A JSON packet to send to the debugging server.
- * @param aOnResponse function
- * If specified, will be called with the JSON response packet when
- * debugging server responds.
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- * It is also a Promise object, with a `then` method, that is resolved
- * whenever a JSON or a Bulk response is received; and is rejected
- * if the response is an error.
- * Note: This return value can be ignored if you are using JSON alone,
- * because the callback provided in |aOnResponse| will be bound to the
- * "json-reply" event automatically.
- *
- * Events emitted:
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- request: function (aRequest, aOnResponse) {
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- let type = aRequest.type || "";
- if (!aRequest.to) {
- throw Error("'" + type + "' request packet has no destination.");
- }
- if (this._closed) {
- let msg = "'" + type + "' request packet to " +
- "'" + aRequest.to + "' " +
- "can't be sent as the connection is closed.";
- let resp = { error: "connectionClosed", message: msg };
- if (aOnResponse) {
- aOnResponse(resp);
- }
- return promise.reject(resp);
- }
- let request = new Request(aRequest);
- request.format = "json";
- request.stack = getStack();
- if (aOnResponse) {
- request.on("json-reply", aOnResponse);
- }
- this._sendOrQueueRequest(request);
- // Implement a Promise like API on the returned object
- // that resolves/rejects on request response
- let deferred = promise.defer();
- function listenerJson(resp) {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- if (resp.error) {
- deferred.reject(resp);
- } else {
- deferred.resolve(resp);
- }
- }
- function listenerBulk(resp) {
- request.off("json-reply", listenerJson);
- request.off("bulk-reply", listenerBulk);
- deferred.resolve(resp);
- }
- request.on("json-reply", listenerJson);
- request.on("bulk-reply", listenerBulk);
- request.then = deferred.promise.then.bind(deferred.promise);
- return request;
- },
- /**
- * Transmit streaming data via a bulk request.
- *
- * This method initiates the bulk send process by queuing up the header data.
- * The caller receives eventual access to a stream for writing.
- *
- * Since this opens up more options for how the server might respond (it could
- * send back either JSON or bulk data), and the returned Request object emits
- * events for different stages of the request process that you may want to
- * react to.
- *
- * @param request Object
- * This is modeled after the format of JSON packets above, but does not
- * actually contain the data, but is instead just a routing header:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be sent
- * @return Request
- * This object emits a number of events to allow you to respond to
- * different parts of the request lifecycle.
- *
- * Events emitted:
- * * bulk-send-ready: Ready to send bulk data to the server, using the
- * event data object containing:
- * * stream: This output stream should only be used directly if
- * you can ensure that you will write exactly |length|
- * bytes and will not close the stream when writing is
- * complete
- * * done: If you use the stream directly (instead of |copyFrom|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as
- * a rejection value, it will be logged via |dumpn|. If
- * you do use |copyFrom|, resolving is taken care of for
- * you when copying completes.
- * * copyFrom: A helper function for getting your data onto the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param input nsIAsyncInputStream
- * The stream to copy from.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- * * json-reply: The server replied with a JSON packet, which is
- * passed as event data.
- * * bulk-reply: The server replied with bulk data, which you can read
- * using the event data object containing:
- * * actor: Name of actor that received the packet
- * * type: Name of actor's method that was called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you
- * can ensure that you will read exactly |length| bytes
- * and will not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the
- * transport will be closed. If an Error is supplied as a
- * rejection value, it will be logged via |dumpn|. If you
- * do use |copyTo|, resolving is taken care of for you
- * when copying completes.
- * * copyTo: A helper function for getting your data out of the
- * stream that meets the stream handling requirements
- * above, and has the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or
- * rejected if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- startBulkRequest: function (request) {
- if (!this.traits.bulk) {
- throw Error("Server doesn't support bulk transfers");
- }
- if (!this.mainRoot) {
- throw Error("Have not yet received a hello packet from the server.");
- }
- if (!request.type) {
- throw Error("Bulk packet is missing the required 'type' field.");
- }
- if (!request.actor) {
- throw Error("'" + request.type + "' bulk packet has no destination.");
- }
- if (!request.length) {
- throw Error("'" + request.type + "' bulk packet has no length.");
- }
- request = new Request(request);
- request.format = "bulk";
- this._sendOrQueueRequest(request);
- return request;
- },
- /**
- * If a new request can be sent immediately, do so. Otherwise, queue it.
- */
- _sendOrQueueRequest(request) {
- let actor = request.actor;
- if (!this._activeRequests.has(actor)) {
- this._sendRequest(request);
- } else {
- this._queueRequest(request);
- }
- },
- /**
- * Send a request.
- * @throws Error if there is already an active request in flight for the same
- * actor.
- */
- _sendRequest(request) {
- let actor = request.actor;
- this.expectReply(actor, request);
- if (request.format === "json") {
- this._transport.send(request.request);
- return false;
- }
- this._transport.startBulkSend(request.request).then((...args) => {
- request.emit("bulk-send-ready", ...args);
- });
- },
- /**
- * Queue a request to be sent later. Queues are only drained when an in
- * flight request to a given actor completes.
- */
- _queueRequest(request) {
- let actor = request.actor;
- let queue = this._pendingRequests.get(actor) || [];
- queue.push(request);
- this._pendingRequests.set(actor, queue);
- },
- /**
- * Attempt the next request to a given actor (if any).
- */
- _attemptNextRequest(actor) {
- if (this._activeRequests.has(actor)) {
- return;
- }
- let queue = this._pendingRequests.get(actor);
- if (!queue) {
- return;
- }
- let request = queue.shift();
- if (queue.length === 0) {
- this._pendingRequests.delete(actor);
- }
- this._sendRequest(request);
- },
- /**
- * Arrange to hand the next reply from |aActor| to the handler bound to
- * |aRequest|.
- *
- * DebuggerClient.prototype.request / startBulkRequest usually takes care of
- * establishing the handler for a given request, but in rare cases (well,
- * greetings from new root actors, is the only case at the moment) we must be
- * prepared for a "reply" that doesn't correspond to any request we sent.
- */
- expectReply: function (aActor, aRequest) {
- if (this._activeRequests.has(aActor)) {
- throw Error("clashing handlers for next reply from " + uneval(aActor));
- }
- // If a handler is passed directly (as it is with the handler for the root
- // actor greeting), create a dummy request to bind this to.
- if (typeof aRequest === "function") {
- let handler = aRequest;
- aRequest = new Request();
- aRequest.on("json-reply", handler);
- }
- this._activeRequests.set(aActor, aRequest);
- },
- // Transport hooks.
- /**
- * Called by DebuggerTransport to dispatch incoming packets as appropriate.
- *
- * @param aPacket object
- * The incoming packet.
- */
- onPacket: function (aPacket) {
- if (!aPacket.from) {
- DevToolsUtils.reportException(
- "onPacket",
- new Error("Server did not specify an actor, dropping packet: " +
- JSON.stringify(aPacket)));
- return;
- }
- // If we have a registered Front for this actor, let it handle the packet
- // and skip all the rest of this unpleasantness.
- let front = this.getActor(aPacket.from);
- if (front) {
- front.onPacket(aPacket);
- return;
- }
- // Check for "forwardingCancelled" here instead of using a client to handle it.
- // This is necessary because we might receive this event while the client is closing,
- // and the clients have already been removed by that point.
- if (this.mainRoot &&
- aPacket.from == this.mainRoot.actor &&
- aPacket.type == "forwardingCancelled") {
- this.purgeRequests(aPacket.prefix);
- return;
- }
- if (this._clients.has(aPacket.from) && aPacket.type) {
- let client = this._clients.get(aPacket.from);
- let type = aPacket.type;
- if (client.events.indexOf(type) != -1) {
- client.emit(type, aPacket);
- // we ignore the rest, as the client is expected to handle this packet.
- return;
- }
- }
- let activeRequest;
- // See if we have a handler function waiting for a reply from this
- // actor. (Don't count unsolicited notifications or pauses as
- // replies.)
- if (this._activeRequests.has(aPacket.from) &&
- !(aPacket.type in UnsolicitedNotifications) &&
- !(aPacket.type == ThreadStateTypes.paused &&
- aPacket.why.type in UnsolicitedPauses)) {
- activeRequest = this._activeRequests.get(aPacket.from);
- this._activeRequests.delete(aPacket.from);
- }
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(aPacket.from);
- // Packets that indicate thread state changes get special treatment.
- if (aPacket.type in ThreadStateTypes &&
- this._clients.has(aPacket.from) &&
- typeof this._clients.get(aPacket.from)._onThreadState == "function") {
- this._clients.get(aPacket.from)._onThreadState(aPacket);
- }
- // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
- if (!this.traits.noNeedToFakeResumptionOnNavigation) {
- // On navigation the server resumes, so the client must resume as well.
- // We achieve that by generating a fake resumption packet that triggers
- // the client's thread state change listeners.
- if (aPacket.type == UnsolicitedNotifications.tabNavigated &&
- this._clients.has(aPacket.from) &&
- this._clients.get(aPacket.from).thread) {
- let thread = this._clients.get(aPacket.from).thread;
- let resumption = { from: thread._actor, type: "resumed" };
- thread._onThreadState(resumption);
- }
- }
- // Only try to notify listeners on events, not responses to requests
- // that lack a packet type.
- if (aPacket.type) {
- this.emit(aPacket.type, aPacket);
- }
- if (activeRequest) {
- let emitReply = () => activeRequest.emit("json-reply", aPacket);
- if (activeRequest.stack) {
- callFunctionWithAsyncStack(emitReply, activeRequest.stack,
- "DevTools RDP");
- } else {
- emitReply();
- }
- }
- },
- /**
- * Called by the DebuggerTransport to dispatch incoming bulk packets as
- * appropriate.
- *
- * @param packet object
- * The incoming packet, which contains:
- * * actor: Name of actor that will receive the packet
- * * type: Name of actor's method that should be called on receipt
- * * length: Size of the data to be read
- * * stream: This input stream should only be used directly if you can
- * ensure that you will read exactly |length| bytes and will
- * not close the stream when reading is complete
- * * done: If you use the stream directly (instead of |copyTo|
- * below), you must signal completion by resolving /
- * rejecting this deferred. If it's rejected, the transport
- * will be closed. If an Error is supplied as a rejection
- * value, it will be logged via |dumpn|. If you do use
- * |copyTo|, resolving is taken care of for you when copying
- * completes.
- * * copyTo: A helper function for getting your data out of the stream
- * that meets the stream handling requirements above, and has
- * the following signature:
- * @param output nsIAsyncOutputStream
- * The stream to copy to.
- * @return Promise
- * The promise is resolved when copying completes or rejected
- * if any (unexpected) errors occur.
- * This object also emits "progress" events for each chunk
- * that is copied. See stream-utils.js.
- */
- onBulkPacket: function (packet) {
- let { actor, type, length } = packet;
- if (!actor) {
- DevToolsUtils.reportException(
- "onBulkPacket",
- new Error("Server did not specify an actor, dropping bulk packet: " +
- JSON.stringify(packet)));
- return;
- }
- // See if we have a handler function waiting for a reply from this
- // actor.
- if (!this._activeRequests.has(actor)) {
- return;
- }
- let activeRequest = this._activeRequests.get(actor);
- this._activeRequests.delete(actor);
- // If there is a subsequent request for the same actor, hand it off to the
- // transport. Delivery of packets on the other end is always async, even
- // in the local transport case.
- this._attemptNextRequest(actor);
- activeRequest.emit("bulk-reply", packet);
- },
- /**
- * Called by DebuggerTransport when the underlying stream is closed.
- *
- * @param aStatus nsresult
- * The status code that corresponds to the reason for closing
- * the stream.
- */
- onClosed: function () {
- this._closed = true;
- this.emit("closed");
- this.purgeRequests();
- // The |_pools| array on the client-side currently is used only by
- // protocol.js to store active fronts, mirroring the actor pools found in
- // the server. So, read all usages of "pool" as "protocol.js front".
- //
- // In the normal case where we shutdown cleanly, the toolbox tells each tool
- // to close, and they each call |destroy| on any fronts they were using.
- // When |destroy| or |cleanup| is called on a protocol.js front, it also
- // removes itself from the |_pools| array. Once the toolbox has shutdown,
- // the connection is closed, and we reach here. All fronts (should have
- // been) |destroy|ed, so |_pools| should empty.
- //
- // If the connection instead aborts unexpectedly, we may end up here with
- // all fronts used during the life of the connection. So, we call |cleanup|
- // on them clear their state, reject pending requests, and remove themselves
- // from |_pools|. This saves the toolbox from hanging indefinitely, in case
- // it waits for some server response before shutdown that will now never
- // arrive.
- for (let pool of this._pools) {
- pool.cleanup();
- }
- },
- /**
- * Purge pending and active requests in this client.
- *
- * @param prefix string (optional)
- * If a prefix is given, only requests for actor IDs that start with the prefix
- * will be cleaned up. This is useful when forwarding of a portion of requests
- * is cancelled on the server.
- */
- purgeRequests(prefix = "") {
- let reject = function (type, request) {
- // Server can send packets on its own and client only pass a callback
- // to expectReply, so that there is no request object.
- let msg;
- if (request.request) {
- msg = "'" + request.request.type + "' " + type + " request packet" +
- " to '" + request.actor + "' " +
- "can't be sent as the connection just closed.";
- } else {
- msg = "server side packet can't be received as the connection just closed.";
- }
- let packet = { error: "connectionClosed", message: msg };
- request.emit("json-reply", packet);
- };
- let pendingRequestsToReject = [];
- this._pendingRequests.forEach((requests, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._pendingRequests.delete(actor);
- pendingRequestsToReject = pendingRequestsToReject.concat(requests);
- });
- pendingRequestsToReject.forEach(request => reject("pending", request));
- let activeRequestsToReject = [];
- this._activeRequests.forEach((request, actor) => {
- if (!actor.startsWith(prefix)) {
- return;
- }
- this._activeRequests.delete(actor);
- activeRequestsToReject = activeRequestsToReject.concat(request);
- });
- activeRequestsToReject.forEach(request => reject("active", request));
- },
- registerClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `actor` attribute.");
- }
- if (!Array.isArray(client.events)) {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with an `events` attribute " +
- "that is an array.");
- }
- if (client.events.length > 0 && typeof (client.emit) != "function") {
- throw new Error("DebuggerServer.registerClient expects " +
- "a client instance with non-empty `events` array to" +
- "have an `emit` function.");
- }
- if (this._clients.has(actorID)) {
- throw new Error("DebuggerServer.registerClient already registered " +
- "a client for this actor.");
- }
- this._clients.set(actorID, client);
- },
- unregisterClient: function (client) {
- let actorID = client.actor;
- if (!actorID) {
- throw new Error("DebuggerServer.unregisterClient expects " +
- "a Client instance with a `actor` attribute.");
- }
- this._clients.delete(actorID);
- },
- /**
- * Actor lifetime management, echos the server's actor pools.
- */
- __pools: null,
- get _pools() {
- if (this.__pools) {
- return this.__pools;
- }
- this.__pools = new Set();
- return this.__pools;
- },
- addActorPool: function (pool) {
- this._pools.add(pool);
- },
- removeActorPool: function (pool) {
- this._pools.delete(pool);
- },
- getActor: function (actorID) {
- let pool = this.poolFor(actorID);
- return pool ? pool.get(actorID) : null;
- },
- poolFor: function (actorID) {
- for (let pool of this._pools) {
- if (pool.has(actorID)) return pool;
- }
- return null;
- },
- /**
- * Currently attached addon.
- */
- activeAddon: null
- };
- eventSource(DebuggerClient.prototype);
- function Request(request) {
- this.request = request;
- }
- Request.prototype = {
- on: function (type, listener) {
- events.on(this, type, listener);
- },
- off: function (type, listener) {
- events.off(this, type, listener);
- },
- once: function (type, listener) {
- events.once(this, type, listener);
- },
- emit: function (type, ...args) {
- events.emit(this, type, ...args);
- },
- get actor() { return this.request.to || this.request.actor; }
- };
- /**
- * Creates a tab client for the remote debugging protocol server. This client
- * is a front to the tab actor created in the server side, hiding the protocol
- * details in a traditional JavaScript API.
- *
- * @param aClient DebuggerClient
- * The debugger client parent.
- * @param aForm object
- * The protocol form for this tab.
- */
- function TabClient(aClient, aForm) {
- this.client = aClient;
- this._actor = aForm.from;
- this._threadActor = aForm.threadActor;
- this.javascriptEnabled = aForm.javascriptEnabled;
- this.cacheDisabled = aForm.cacheDisabled;
- this.thread = null;
- this.request = this.client.request;
- this.traits = aForm.traits || {};
- this.events = ["workerListChanged"];
- }
- TabClient.prototype = {
- get actor() { return this._actor; },
- get _transport() { return this.client._transport; },
- /**
- * Attach to a thread actor.
- *
- * @param object aOptions
- * Configuration options.
- * - useSourceMaps: whether to use source maps or not.
- * @param function aOnResponse
- * Called with the response packet and a ThreadClient
- * (which will be undefined on error).
- */
- attachThread: function (aOptions = {}, aOnResponse = noop) {
- if (this.thread) {
- DevToolsUtils.executeSoon(() => aOnResponse({}, this.thread));
- return promise.resolve([{}, this.thread]);
- }
- let packet = {
- to: this._threadActor,
- type: "attach",
- options: aOptions
- };
- return this.request(packet).then(aResponse => {
- if (!aResponse.error) {
- this.thread = new ThreadClient(this, this._threadActor);
- this.client.registerClient(this.thread);
- }
- aOnResponse(aResponse, this.thread);
- return [aResponse, this.thread];
- });
- },
- /**
- * Detach the client from the tab actor.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- before: function (aPacket) {
- if (this.thread) {
- this.thread.detach();
- }
- return aPacket;
- },
- after: function (aResponse) {
- this.client.unregisterClient(this);
- return aResponse;
- },
- }),
- /**
- * Bring the window to the front.
- */
- focus: DebuggerClient.requester({
- type: "focus"
- }, {}),
- /**
- * Reload the page in this tab.
- *
- * @param [optional] object options
- * An object with a `force` property indicating whether or not
- * this reload should skip the cache
- */
- reload: function (options = { force: false }) {
- return this._reload(options);
- },
- _reload: DebuggerClient.requester({
- type: "reload",
- options: args(0)
- }),
- /**
- * Navigate to another URL.
- *
- * @param string url
- * The URL to navigate to.
- */
- navigateTo: DebuggerClient.requester({
- type: "navigateTo",
- url: args(0)
- }),
- /**
- * Reconfigure the tab actor.
- *
- * @param object aOptions
- * A dictionary object of the new options to use in the tab actor.
- * @param function aOnResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: args(0)
- }),
- listWorkers: DebuggerClient.requester({
- type: "listWorkers"
- }),
- attachWorker: function (aWorkerActor, aOnResponse) {
- this.client.attachWorker(aWorkerActor, aOnResponse);
- },
- /**
- * Resolve a location ({ url, line, column }) to its current
- * source mapping location.
- *
- * @param {String} arg[0].url
- * @param {Number} arg[0].line
- * @param {Number?} arg[0].column
- */
- resolveLocation: DebuggerClient.requester({
- type: "resolveLocation",
- location: args(0)
- }),
- };
- eventSource(TabClient.prototype);
- function WorkerClient(aClient, aForm) {
- this.client = aClient;
- this._actor = aForm.from;
- this._isClosed = false;
- this._url = aForm.url;
- this._onClose = this._onClose.bind(this);
- this.addListener("close", this._onClose);
- this.traits = {};
- }
- WorkerClient.prototype = {
- get _transport() {
- return this.client._transport;
- },
- get request() {
- return this.client.request;
- },
- get actor() {
- return this._actor;
- },
- get url() {
- return this._url;
- },
- get isClosed() {
- return this._isClosed;
- },
- detach: DebuggerClient.requester({ type: "detach" }, {
- after: function (aResponse) {
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- return aResponse;
- },
- }),
- attachThread: function (aOptions = {}, aOnResponse = noop) {
- if (this.thread) {
- let response = [{
- type: "connected",
- threadActor: this.thread._actor,
- consoleActor: this.consoleActor,
- }, this.thread];
- DevToolsUtils.executeSoon(() => aOnResponse(response));
- return response;
- }
- // The connect call on server doesn't attach the thread as of version 44.
- return this.request({
- to: this._actor,
- type: "connect",
- options: aOptions,
- }).then(connectReponse => {
- if (connectReponse.error) {
- aOnResponse(connectReponse, null);
- return [connectResponse, null];
- }
- return this.request({
- to: connectReponse.threadActor,
- type: "attach",
- options: aOptions
- }).then(attachResponse => {
- if (attachResponse.error) {
- aOnResponse(attachResponse, null);
- }
- this.thread = new ThreadClient(this, connectReponse.threadActor);
- this.consoleActor = connectReponse.consoleActor;
- this.client.registerClient(this.thread);
- aOnResponse(connectReponse, this.thread);
- return [connectResponse, this.thread];
- });
- }, error => {
- aOnResponse(error, null);
- });
- },
- _onClose: function () {
- this.removeListener("close", this._onClose);
- if (this.thread) {
- this.client.unregisterClient(this.thread);
- }
- this.client.unregisterClient(this);
- this._isClosed = true;
- },
- reconfigure: function () {
- return Promise.resolve();
- },
- events: ["close"]
- };
- eventSource(WorkerClient.prototype);
- function AddonClient(aClient, aActor) {
- this._client = aClient;
- this._actor = aActor;
- this.request = this._client.request;
- this.events = [];
- }
- AddonClient.prototype = {
- get actor() { return this._actor; },
- get _transport() { return this._client._transport; },
- /**
- * Detach the client from the addon actor.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (aResponse) {
- if (this._client.activeAddon === this) {
- this._client.activeAddon = null;
- }
- this._client.unregisterClient(this);
- return aResponse;
- },
- })
- };
- /**
- * A RootClient object represents a root actor on the server. Each
- * DebuggerClient keeps a RootClient instance representing the root actor
- * for the initial connection; DebuggerClient's 'listTabs' and
- * 'listChildProcesses' methods forward to that root actor.
- *
- * @param aClient object
- * The client connection to which this actor belongs.
- * @param aGreeting string
- * The greeting packet from the root actor we're to represent.
- *
- * Properties of a RootClient instance:
- *
- * @property actor string
- * The name of this child's root actor.
- * @property applicationType string
- * The application type, as given in the root actor's greeting packet.
- * @property traits object
- * The traits object, as given in the root actor's greeting packet.
- */
- function RootClient(aClient, aGreeting) {
- this._client = aClient;
- this.actor = aGreeting.from;
- this.applicationType = aGreeting.applicationType;
- this.traits = aGreeting.traits;
- }
- exports.RootClient = RootClient;
- RootClient.prototype = {
- constructor: RootClient,
- /**
- * List the open tabs.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- listTabs: DebuggerClient.requester({ type: "listTabs" }),
- /**
- * List the installed addons.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- listAddons: DebuggerClient.requester({ type: "listAddons" }),
- /**
- * List the registered workers.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
- /**
- * List the registered service workers.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- listServiceWorkerRegistrations: DebuggerClient.requester({
- type: "listServiceWorkerRegistrations"
- }),
- /**
- * List the running processes.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
- /**
- * Fetch the TabActor for the currently selected tab, or for a specific
- * tab given as first parameter.
- *
- * @param [optional] object aFilter
- * A dictionary object with following optional attributes:
- * - outerWindowID: used to match tabs in parent process
- * - tabId: used to match tabs in child processes
- * - tab: a reference to xul:tab element
- * If nothing is specified, returns the actor for the currently
- * selected tab.
- */
- getTab: function (aFilter) {
- let packet = {
- to: this.actor,
- type: "getTab"
- };
- if (aFilter) {
- if (typeof (aFilter.outerWindowID) == "number") {
- packet.outerWindowID = aFilter.outerWindowID;
- } else if (typeof (aFilter.tabId) == "number") {
- packet.tabId = aFilter.tabId;
- } else if ("tab" in aFilter) {
- let browser = aFilter.tab.linkedBrowser;
- if (browser.frameLoader.tabParent) {
- // Tabs in child process
- packet.tabId = browser.frameLoader.tabParent.tabId;
- } else if (browser.outerWindowID) {
- // <xul:browser> tabs in parent process
- packet.outerWindowID = browser.outerWindowID;
- } else {
- // <iframe mozbrowser> tabs in parent process
- let windowUtils = browser.contentWindow
- .QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils);
- packet.outerWindowID = windowUtils.outerWindowID;
- }
- } else {
- // Throw if a filter object have been passed but without
- // any clearly idenfified filter.
- throw new Error("Unsupported argument given to getTab request");
- }
- }
- return this.request(packet);
- },
- /**
- * Description of protocol's actors and methods.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
- /*
- * Methods constructed by DebuggerClient.requester require these forwards
- * on their 'this'.
- */
- get _transport() { return this._client._transport; },
- get request() { return this._client.request; }
- };
- /**
- * Creates a thread client for the remote debugging protocol server. This client
- * is a front to the thread actor created in the server side, hiding the
- * protocol details in a traditional JavaScript API.
- *
- * @param aClient DebuggerClient|TabClient
- * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
- * for chrome debuggers).
- * @param aActor string
- * The actor ID for this thread.
- */
- function ThreadClient(aClient, aActor) {
- this._parent = aClient;
- this.client = aClient instanceof DebuggerClient ? aClient : aClient.client;
- this._actor = aActor;
- this._frameCache = [];
- this._scriptCache = {};
- this._pauseGrips = {};
- this._threadGrips = {};
- this.request = this.client.request;
- }
- ThreadClient.prototype = {
- _state: "paused",
- get state() { return this._state; },
- get paused() { return this._state === "paused"; },
- _pauseOnExceptions: false,
- _ignoreCaughtExceptions: false,
- _pauseOnDOMEvents: null,
- _actor: null,
- get actor() { return this._actor; },
- get _transport() { return this.client._transport; },
- _assertPaused: function (aCommand) {
- if (!this.paused) {
- throw Error(aCommand + " command sent while not paused. Currently " + this._state);
- }
- },
- /**
- * Resume a paused thread. If the optional aLimit parameter is present, then
- * the thread will also pause when that limit is reached.
- *
- * @param [optional] object aLimit
- * An object with a type property set to the appropriate limit (next,
- * step, or finish) per the remote debugging protocol specification.
- * Use null to specify no limit.
- * @param function aOnResponse
- * Called with the response packet.
- */
- _doResume: DebuggerClient.requester({
- type: "resume",
- resumeLimit: args(0)
- }, {
- before: function (aPacket) {
- this._assertPaused("resume");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._previousState = this._state;
- this._state = "resuming";
- if (this._pauseOnExceptions) {
- aPacket.pauseOnExceptions = this._pauseOnExceptions;
- }
- if (this._ignoreCaughtExceptions) {
- aPacket.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
- }
- if (this._pauseOnDOMEvents) {
- aPacket.pauseOnDOMEvents = this._pauseOnDOMEvents;
- }
- return aPacket;
- },
- after: function (aResponse) {
- if (aResponse.error && this._state == "resuming") {
- // There was an error resuming, update the state to the new one
- // reported by the server, if given (only on wrongState), otherwise
- // reset back to the previous state.
- if (aResponse.state) {
- this._state = ThreadStateTypes[aResponse.state];
- } else {
- this._state = this._previousState;
- }
- }
- delete this._previousState;
- return aResponse;
- },
- }),
- /**
- * Reconfigure the thread actor.
- *
- * @param object aOptions
- * A dictionary object of the new options to use in the thread actor.
- * @param function aOnResponse
- * Called with the response packet.
- */
- reconfigure: DebuggerClient.requester({
- type: "reconfigure",
- options: args(0)
- }),
- /**
- * Resume a paused thread.
- */
- resume: function (aOnResponse) {
- return this._doResume(null, aOnResponse);
- },
- /**
- * Resume then pause without stepping.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- resumeThenPause: function (aOnResponse) {
- return this._doResume({ type: "break" }, aOnResponse);
- },
- /**
- * Step over a function call.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- stepOver: function (aOnResponse) {
- return this._doResume({ type: "next" }, aOnResponse);
- },
- /**
- * Step into a function call.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- stepIn: function (aOnResponse) {
- return this._doResume({ type: "step" }, aOnResponse);
- },
- /**
- * Step out of a function call.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- stepOut: function (aOnResponse) {
- return this._doResume({ type: "finish" }, aOnResponse);
- },
- /**
- * Immediately interrupt a running thread.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- interrupt: function (aOnResponse) {
- return this._doInterrupt(null, aOnResponse);
- },
- /**
- * Pause execution right before the next JavaScript bytecode is executed.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- breakOnNext: function (aOnResponse) {
- return this._doInterrupt("onNext", aOnResponse);
- },
- /**
- * Interrupt a running thread.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- _doInterrupt: DebuggerClient.requester({
- type: "interrupt",
- when: args(0)
- }),
- /**
- * Enable or disable pausing when an exception is thrown.
- *
- * @param boolean aFlag
- * Enables pausing if true, disables otherwise.
- * @param function aOnResponse
- * Called with the response packet.
- */
- pauseOnExceptions: function (aPauseOnExceptions,
- aIgnoreCaughtExceptions,
- aOnResponse = noop) {
- this._pauseOnExceptions = aPauseOnExceptions;
- this._ignoreCaughtExceptions = aIgnoreCaughtExceptions;
- // Otherwise send the flag using a standard resume request.
- if (!this.paused) {
- return this.interrupt(aResponse => {
- if (aResponse.error) {
- // Can't continue if pausing failed.
- aOnResponse(aResponse);
- return aResponse;
- }
- return this.resume(aOnResponse);
- });
- }
- aOnResponse();
- return promise.resolve();
- },
- /**
- * Enable pausing when the specified DOM events are triggered. Disabling
- * pausing on an event can be realized by calling this method with the updated
- * array of events that doesn't contain it.
- *
- * @param array|string events
- * An array of strings, representing the DOM event types to pause on,
- * or "*" to pause on all DOM events. Pass an empty array to
- * completely disable pausing on DOM events.
- * @param function onResponse
- * Called with the response packet in a future turn of the event loop.
- */
- pauseOnDOMEvents: function (events, onResponse = noop) {
- this._pauseOnDOMEvents = events;
- // If the debuggee is paused, the value of the array will be communicated in
- // the next resumption. Otherwise we have to force a pause in order to send
- // the array.
- if (this.paused) {
- DevToolsUtils.executeSoon(() => onResponse({}));
- return {};
- }
- return this.interrupt(response => {
- // Can't continue if pausing failed.
- if (response.error) {
- onResponse(response);
- return response;
- }
- return this.resume(onResponse);
- });
- },
- /**
- * Send a clientEvaluate packet to the debuggee. Response
- * will be a resume packet.
- *
- * @param string aFrame
- * The actor ID of the frame where the evaluation should take place.
- * @param string aExpression
- * The expression that will be evaluated in the scope of the frame
- * above.
- * @param function aOnResponse
- * Called with the response packet.
- */
- eval: DebuggerClient.requester({
- type: "clientEvaluate",
- frame: args(0),
- expression: args(1)
- }, {
- before: function (aPacket) {
- this._assertPaused("eval");
- // Put the client in a tentative "resuming" state so we can prevent
- // further requests that should only be sent in the paused state.
- this._state = "resuming";
- return aPacket;
- },
- after: function (aResponse) {
- if (aResponse.error) {
- // There was an error resuming, back to paused state.
- this._state = "paused";
- }
- return aResponse;
- },
- }),
- /**
- * Detach from the thread actor.
- *
- * @param function aOnResponse
- * Called with the response packet.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (aResponse) {
- this.client.unregisterClient(this);
- this._parent.thread = null;
- return aResponse;
- },
- }),
- /**
- * Release multiple thread-lifetime object actors. If any pause-lifetime
- * actors are included in the request, a |notReleasable| error will return,
- * but all the thread-lifetime ones will have been released.
- *
- * @param array actors
- * An array with actor IDs to release.
- */
- releaseMany: DebuggerClient.requester({
- type: "releaseMany",
- actors: args(0),
- }),
- /**
- * Promote multiple pause-lifetime object actors to thread-lifetime ones.
- *
- * @param array actors
- * An array with actor IDs to promote.
- */
- threadGrips: DebuggerClient.requester({
- type: "threadGrips",
- actors: args(0)
- }),
- /**
- * Return the event listeners defined on the page.
- *
- * @param aOnResponse Function
- * Called with the thread's response.
- */
- eventListeners: DebuggerClient.requester({
- type: "eventListeners"
- }),
- /**
- * Request the loaded sources for the current thread.
- *
- * @param aOnResponse Function
- * Called with the thread's response.
- */
- getSources: DebuggerClient.requester({
- type: "sources"
- }),
- /**
- * Clear the thread's source script cache. A scriptscleared event
- * will be sent.
- */
- _clearScripts: function () {
- if (Object.keys(this._scriptCache).length > 0) {
- this._scriptCache = {};
- this.emit("scriptscleared");
- }
- },
- /**
- * Request frames from the callstack for the current thread.
- *
- * @param aStart integer
- * The number of the youngest stack frame to return (the youngest
- * frame is 0).
- * @param aCount integer
- * The maximum number of frames to return, or null to return all
- * frames.
- * @param aOnResponse function
- * Called with the thread's response.
- */
- getFrames: DebuggerClient.requester({
- type: "frames",
- start: args(0),
- count: args(1)
- }),
- /**
- * An array of cached frames. Clients can observe the framesadded and
- * framescleared event to keep up to date on changes to this cache,
- * and can fill it using the fillFrames method.
- */
- get cachedFrames() { return this._frameCache; },
- /**
- * true if there are more stack frames available on the server.
- */
- get moreFrames() {
- return this.paused && (!this._frameCache || this._frameCache.length == 0
- || !this._frameCache[this._frameCache.length - 1].oldest);
- },
- /**
- * Ensure that at least aTotal stack frames have been loaded in the
- * ThreadClient's stack frame cache. A framesadded event will be
- * sent when the stack frame cache is updated.
- *
- * @param aTotal number
- * The minimum number of stack frames to be included.
- * @param aCallback function
- * Optional callback function called when frames have been loaded
- * @returns true if a framesadded notification should be expected.
- */
- fillFrames: function (aTotal, aCallback = noop) {
- this._assertPaused("fillFrames");
- if (this._frameCache.length >= aTotal) {
- return false;
- }
- let numFrames = this._frameCache.length;
- this.getFrames(numFrames, aTotal - numFrames, (aResponse) => {
- if (aResponse.error) {
- aCallback(aResponse);
- return;
- }
- let threadGrips = DevToolsUtils.values(this._threadGrips);
- for (let i in aResponse.frames) {
- let frame = aResponse.frames[i];
- if (!frame.where.source) {
- // Older servers use urls instead, so we need to resolve
- // them to source actors
- for (let grip of threadGrips) {
- if (grip instanceof SourceClient && grip.url === frame.url) {
- frame.where.source = grip._form;
- }
- }
- }
- this._frameCache[frame.depth] = frame;
- }
- // If we got as many frames as we asked for, there might be more
- // frames available.
- this.emit("framesadded");
- aCallback(aResponse);
- });
- return true;
- },
- /**
- * Clear the thread's stack frame cache. A framescleared event
- * will be sent.
- */
- _clearFrames: function () {
- if (this._frameCache.length > 0) {
- this._frameCache = [];
- this.emit("framescleared");
- }
- },
- /**
- * Return a ObjectClient object for the given object grip.
- *
- * @param aGrip object
- * A pause-lifetime object grip returned by the protocol.
- */
- pauseGrip: function (aGrip) {
- if (aGrip.actor in this._pauseGrips) {
- return this._pauseGrips[aGrip.actor];
- }
- let client = new ObjectClient(this.client, aGrip);
- this._pauseGrips[aGrip.actor] = client;
- return client;
- },
- /**
- * Get or create a long string client, checking the grip client cache if it
- * already exists.
- *
- * @param aGrip Object
- * The long string grip returned by the protocol.
- * @param aGripCacheName String
- * The property name of the grip client cache to check for existing
- * clients in.
- */
- _longString: function (aGrip, aGripCacheName) {
- if (aGrip.actor in this[aGripCacheName]) {
- return this[aGripCacheName][aGrip.actor];
- }
- let client = new LongStringClient(this.client, aGrip);
- this[aGripCacheName][aGrip.actor] = client;
- return client;
- },
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the current pause.
- *
- * @param aGrip Object
- * The long string grip returned by the protocol.
- */
- pauseLongString: function (aGrip) {
- return this._longString(aGrip, "_pauseGrips");
- },
- /**
- * Return an instance of LongStringClient for the given long string grip that
- * is scoped to the thread lifetime.
- *
- * @param aGrip Object
- * The long string grip returned by the protocol.
- */
- threadLongString: function (aGrip) {
- return this._longString(aGrip, "_threadGrips");
- },
- /**
- * Clear and invalidate all the grip clients from the given cache.
- *
- * @param aGripCacheName
- * The property name of the grip cache we want to clear.
- */
- _clearObjectClients: function (aGripCacheName) {
- for (let id in this[aGripCacheName]) {
- this[aGripCacheName][id].valid = false;
- }
- this[aGripCacheName] = {};
- },
- /**
- * Invalidate pause-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearPauseGrips: function () {
- this._clearObjectClients("_pauseGrips");
- },
- /**
- * Invalidate thread-lifetime grip clients and clear the list of current grip
- * clients.
- */
- _clearThreadGrips: function () {
- this._clearObjectClients("_threadGrips");
- },
- /**
- * Handle thread state change by doing necessary cleanup and notifying all
- * registered listeners.
- */
- _onThreadState: function (aPacket) {
- this._state = ThreadStateTypes[aPacket.type];
- // The debugger UI may not be initialized yet so we want to keep
- // the packet around so it knows what to pause state to display
- // when it's initialized
- this._lastPausePacket = aPacket.type === "resumed" ? null : aPacket;
- this._clearFrames();
- this._clearPauseGrips();
- aPacket.type === ThreadStateTypes.detached && this._clearThreadGrips();
- this.client._eventsEnabled && this.emit(aPacket.type, aPacket);
- },
- getLastPausePacket: function () {
- return this._lastPausePacket;
- },
- /**
- * Return an EnvironmentClient instance for the given environment actor form.
- */
- environment: function (aForm) {
- return new EnvironmentClient(this.client, aForm);
- },
- /**
- * Return an instance of SourceClient for the given source actor form.
- */
- source: function (aForm) {
- if (aForm.actor in this._threadGrips) {
- return this._threadGrips[aForm.actor];
- }
- return this._threadGrips[aForm.actor] = new SourceClient(this, aForm);
- },
- /**
- * Request the prototype and own properties of mutlipleObjects.
- *
- * @param aOnResponse function
- * Called with the request's response.
- * @param actors [string]
- * List of actor ID of the queried objects.
- */
- getPrototypesAndProperties: DebuggerClient.requester({
- type: "prototypesAndProperties",
- actors: args(0)
- }),
- events: ["newSource"]
- };
- eventSource(ThreadClient.prototype);
- /**
- * Creates a tracing profiler client for the remote debugging protocol
- * server. This client is a front to the trace actor created on the
- * server side, hiding the protocol details in a traditional
- * JavaScript API.
- *
- * @param aClient DebuggerClient
- * The debugger client parent.
- * @param aActor string
- * The actor ID for this thread.
- */
- function TraceClient(aClient, aActor) {
- this._client = aClient;
- this._actor = aActor;
- this._activeTraces = new Set();
- this._waitingPackets = new Map();
- this._expectedPacket = 0;
- this.request = this._client.request;
- this.events = [];
- }
- TraceClient.prototype = {
- get actor() { return this._actor; },
- get tracing() { return this._activeTraces.size > 0; },
- get _transport() { return this._client._transport; },
- /**
- * Detach from the trace actor.
- */
- detach: DebuggerClient.requester({
- type: "detach"
- }, {
- after: function (aResponse) {
- this._client.unregisterClient(this);
- return aResponse;
- },
- }),
- /**
- * Start a new trace.
- *
- * @param aTrace [string]
- * An array of trace types to be recorded by the new trace.
- *
- * @param aName string
- * The name of the new trace.
- *
- * @param aOnResponse function
- * Called with the request's response.
- */
- startTrace: DebuggerClient.requester({
- type: "startTrace",
- name: args(1),
- trace: args(0)
- }, {
- after: function (aResponse) {
- if (aResponse.error) {
- return aResponse;
- }
- if (!this.tracing) {
- this._waitingPackets.clear();
- this._expectedPacket = 0;
- }
- this._activeTraces.add(aResponse.name);
- return aResponse;
- },
- }),
- /**
- * End a trace. If a name is provided, stop the named
- * trace. Otherwise, stop the most recently started trace.
- *
- * @param aName string
- * The name of the trace to stop.
- *
- * @param aOnResponse function
- * Called with the request's response.
- */
- stopTrace: DebuggerClient.requester({
- type: "stopTrace",
- name: args(0)
- }, {
- after: function (aResponse) {
- if (aResponse.error) {
- return aResponse;
- }
- this._activeTraces.delete(aResponse.name);
- return aResponse;
- },
- })
- };
- /**
- * Grip clients are used to retrieve information about the relevant object.
- *
- * @param aClient DebuggerClient
- * The debugger client parent.
- * @param aGrip object
- * A pause-lifetime object grip returned by the protocol.
- */
- function ObjectClient(aClient, aGrip)
- {
- this._grip = aGrip;
- this._client = aClient;
- this.request = this._client.request;
- }
- exports.ObjectClient = ObjectClient;
- ObjectClient.prototype = {
- get actor() { return this._grip.actor; },
- get _transport() { return this._client._transport; },
- valid: true,
- get isFrozen() {
- return this._grip.frozen;
- },
- get isSealed() {
- return this._grip.sealed;
- },
- get isExtensible() {
- return this._grip.extensible;
- },
- getDefinitionSite: DebuggerClient.requester({
- type: "definitionSite"
- }, {
- before: function (aPacket) {
- if (this._grip.class != "Function") {
- throw new Error("getDefinitionSite is only valid for function grips.");
- }
- return aPacket;
- }
- }),
- /**
- * Request the names of a function's formal parameters.
- *
- * @param aOnResponse function
- * Called with an object of the form:
- * { parameterNames:[<parameterName>, ...] }
- * where each <parameterName> is the name of a parameter.
- */
- getParameterNames: DebuggerClient.requester({
- type: "parameterNames"
- }, {
- before: function (aPacket) {
- if (this._grip["class"] !== "Function") {
- throw new Error("getParameterNames is only valid for function grips.");
- }
- return aPacket;
- },
- }),
- /**
- * Request the names of the properties defined on the object and not its
- * prototype.
- *
- * @param aOnResponse function Called with the request's response.
- */
- getOwnPropertyNames: DebuggerClient.requester({
- type: "ownPropertyNames"
- }),
- /**
- * Request the prototype and own properties of the object.
- *
- * @param aOnResponse function Called with the request's response.
- */
- getPrototypeAndProperties: DebuggerClient.requester({
- type: "prototypeAndProperties"
- }),
- /**
- * Request a PropertyIteratorClient instance to ease listing
- * properties for this object.
- *
- * @param options Object
- * A dictionary object with various boolean attributes:
- * - ignoreIndexedProperties Boolean
- * If true, filters out Array items.
- * e.g. properties names between `0` and `object.length`.
- * - ignoreNonIndexedProperties Boolean
- * If true, filters out items that aren't array items
- * e.g. properties names that are not a number between `0`
- * and `object.length`.
- * - sort Boolean
- * If true, the iterator will sort the properties by name
- * before dispatching them.
- * @param aOnResponse function Called with the client instance.
- */
- enumProperties: DebuggerClient.requester({
- type: "enumProperties",
- options: args(0)
- }, {
- after: function (aResponse) {
- if (aResponse.iterator) {
- return { iterator: new PropertyIteratorClient(this._client, aResponse.iterator) };
- }
- return aResponse;
- },
- }),
- /**
- * Request a PropertyIteratorClient instance to enumerate entries in a
- * Map/Set-like object.
- *
- * @param aOnResponse function Called with the request's response.
- */
- enumEntries: DebuggerClient.requester({
- type: "enumEntries"
- }, {
- before: function (packet) {
- if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
- throw new Error("enumEntries is only valid for Map/Set-like grips.");
- }
- return packet;
- },
- after: function (response) {
- if (response.iterator) {
- return {
- iterator: new PropertyIteratorClient(this._client, response.iterator)
- };
- }
- return response;
- }
- }),
- /**
- * Request the property descriptor of the object's specified property.
- *
- * @param aName string The name of the requested property.
- * @param aOnResponse function Called with the request's response.
- */
- getProperty: DebuggerClient.requester({
- type: "property",
- name: args(0)
- }),
- /**
- * Request the prototype of the object.
- *
- * @param aOnResponse function Called with the request's response.
- */
- getPrototype: DebuggerClient.requester({
- type: "prototype"
- }),
- /**
- * Request the display string of the object.
- *
- * @param aOnResponse function Called with the request's response.
- */
- getDisplayString: DebuggerClient.requester({
- type: "displayString"
- }),
- /**
- * Request the scope of the object.
- *
- * @param aOnResponse function Called with the request's response.
- */
- getScope: DebuggerClient.requester({
- type: "scope"
- }, {
- before: function (aPacket) {
- if (this._grip.class !== "Function") {
- throw new Error("scope is only valid for function grips.");
- }
- return aPacket;
- },
- }),
- /**
- * Request the promises directly depending on the current promise.
- */
- getDependentPromises: DebuggerClient.requester({
- type: "dependentPromises"
- }, {
- before: function (aPacket) {
- if (this._grip.class !== "Promise") {
- throw new Error("getDependentPromises is only valid for promise " +
- "grips.");
- }
- return aPacket;
- }
- }),
- /**
- * Request the stack to the promise's allocation point.
- */
- getPromiseAllocationStack: DebuggerClient.requester({
- type: "allocationStack"
- }, {
- before: function (aPacket) {
- if (this._grip.class !== "Promise") {
- throw new Error("getAllocationStack is only valid for promise grips.");
- }
- return aPacket;
- }
- }),
- /**
- * Request the stack to the promise's fulfillment point.
- */
- getPromiseFulfillmentStack: DebuggerClient.requester({
- type: "fulfillmentStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseFulfillmentStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- }),
- /**
- * Request the stack to the promise's rejection point.
- */
- getPromiseRejectionStack: DebuggerClient.requester({
- type: "rejectionStack"
- }, {
- before: function (packet) {
- if (this._grip.class !== "Promise") {
- throw new Error("getPromiseRejectionStack is only valid for " +
- "promise grips.");
- }
- return packet;
- }
- })
- };
- /**
- * A PropertyIteratorClient provides a way to access to property names and
- * values of an object efficiently, slice by slice.
- * Note that the properties can be sorted in the backend,
- * this is controled while creating the PropertyIteratorClient
- * from ObjectClient.enumProperties.
- *
- * @param aClient DebuggerClient
- * The debugger client parent.
- * @param aGrip Object
- * A PropertyIteratorActor grip returned by the protocol via
- * TabActor.enumProperties request.
- */
- function PropertyIteratorClient(aClient, aGrip) {
- this._grip = aGrip;
- this._client = aClient;
- this.request = this._client.request;
- }
- PropertyIteratorClient.prototype = {
- get actor() { return this._grip.actor; },
- /**
- * Get the total number of properties available in the iterator.
- */
- get count() { return this._grip.count; },
- /**
- * Get one or more property names that correspond to the positions in the
- * indexes parameter.
- *
- * @param indexes Array
- * An array of property indexes.
- * @param aCallback Function
- * The function called when we receive the property names.
- */
- names: DebuggerClient.requester({
- type: "names",
- indexes: args(0)
- }, {}),
- /**
- * Get a set of following property value(s).
- *
- * @param start Number
- * The index of the first property to fetch.
- * @param count Number
- * The number of properties to fetch.
- * @param aCallback Function
- * The function called when we receive the property values.
- */
- slice: DebuggerClient.requester({
- type: "slice",
- start: args(0),
- count: args(1)
- }, {}),
- /**
- * Get all the property values.
- *
- * @param aCallback Function
- * The function called when we receive the property values.
- */
- all: DebuggerClient.requester({
- type: "all"
- }, {}),
- };
- /**
- * A LongStringClient provides a way to access "very long" strings from the
- * debugger server.
- *
- * @param aClient DebuggerClient
- * The debugger client parent.
- * @param aGrip Object
- * A pause-lifetime long string grip returned by the protocol.
- */
- function LongStringClient(aClient, aGrip) {
- this._grip = aGrip;
- this._client = aClient;
- this.request = this._client.request;
- }
- exports.LongStringClient = LongStringClient;
- LongStringClient.prototype = {
- get actor() { return this._grip.actor; },
- get length() { return this._grip.length; },
- get initial() { return this._grip.initial; },
- get _transport() { return this._client._transport; },
- valid: true,
- /**
- * Get the substring of this LongString from aStart to aEnd.
- *
- * @param aStart Number
- * The starting index.
- * @param aEnd Number
- * The ending index.
- * @param aCallback Function
- * The function called when we receive the substring.
- */
- substring: DebuggerClient.requester({
- type: "substring",
- start: args(0),
- end: args(1)
- }),
- };
- /**
- * A SourceClient provides a way to access the source text of a script.
- *
- * @param aClient ThreadClient
- * The thread client parent.
- * @param aForm Object
- * The form sent across the remote debugging protocol.
- */
- function SourceClient(aClient, aForm) {
- this._form = aForm;
- this._isBlackBoxed = aForm.isBlackBoxed;
- this._isPrettyPrinted = aForm.isPrettyPrinted;
- this._activeThread = aClient;
- this._client = aClient.client;
- }
- SourceClient.prototype = {
- get _transport() {
- return this._client._transport;
- },
- get isBlackBoxed() {
- return this._isBlackBoxed;
- },
- get isPrettyPrinted() {
- return this._isPrettyPrinted;
- },
- get actor() {
- return this._form.actor;
- },
- get request() {
- return this._client.request;
- },
- get url() {
- return this._form.url;
- },
- /**
- * Black box this SourceClient's source.
- *
- * @param aCallback Function
- * The callback function called when we receive the response from the server.
- */
- blackBox: DebuggerClient.requester({
- type: "blackbox"
- }, {
- after: function (aResponse) {
- if (!aResponse.error) {
- this._isBlackBoxed = true;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return aResponse;
- }
- }),
- /**
- * Un-black box this SourceClient's source.
- *
- * @param aCallback Function
- * The callback function called when we receive the response from the server.
- */
- unblackBox: DebuggerClient.requester({
- type: "unblackbox"
- }, {
- after: function (aResponse) {
- if (!aResponse.error) {
- this._isBlackBoxed = false;
- if (this._activeThread) {
- this._activeThread.emit("blackboxchange", this);
- }
- }
- return aResponse;
- }
- }),
- /**
- * Get Executable Lines from a source
- *
- * @param aCallback Function
- * The callback function called when we receive the response from the server.
- */
- getExecutableLines: function (cb = noop) {
- let packet = {
- to: this._form.actor,
- type: "getExecutableLines"
- };
- return this._client.request(packet).then(res => {
- cb(res.lines);
- return res.lines;
- });
- },
- /**
- * Get a long string grip for this SourceClient's source.
- */
- source: function (aCallback = noop) {
- let packet = {
- to: this._form.actor,
- type: "source"
- };
- return this._client.request(packet).then(aResponse => {
- return this._onSourceResponse(aResponse, aCallback);
- });
- },
- /**
- * Pretty print this source's text.
- */
- prettyPrint: function (aIndent, aCallback = noop) {
- const packet = {
- to: this._form.actor,
- type: "prettyPrint",
- indent: aIndent
- };
- return this._client.request(packet).then(aResponse => {
- if (!aResponse.error) {
- this._isPrettyPrinted = true;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(aResponse, aCallback);
- });
- },
- /**
- * Stop pretty printing this source's text.
- */
- disablePrettyPrint: function (aCallback = noop) {
- const packet = {
- to: this._form.actor,
- type: "disablePrettyPrint"
- };
- return this._client.request(packet).then(aResponse => {
- if (!aResponse.error) {
- this._isPrettyPrinted = false;
- this._activeThread._clearFrames();
- this._activeThread.emit("prettyprintchange", this);
- }
- return this._onSourceResponse(aResponse, aCallback);
- });
- },
- _onSourceResponse: function (aResponse, aCallback) {
- if (aResponse.error) {
- aCallback(aResponse);
- return aResponse;
- }
- if (typeof aResponse.source === "string") {
- aCallback(aResponse);
- return aResponse;
- }
- let { contentType, source } = aResponse;
- let longString = this._activeThread.threadLongString(source);
- return longString.substring(0, longString.length).then(function (aResponse) {
- if (aResponse.error) {
- aCallback(aResponse);
- return aReponse;
- }
- let response = {
- source: aResponse.substring,
- contentType: contentType
- };
- aCallback(response);
- return response;
- });
- },
- /**
- * Request to set a breakpoint in the specified location.
- *
- * @param object aLocation
- * The location and condition of the breakpoint in
- * the form of { line[, column, condition] }.
- * @param function aOnResponse
- * Called with the thread's response.
- */
- setBreakpoint: function ({ line, column, condition, noSliding }, aOnResponse = noop) {
- // A helper function that sets the breakpoint.
- let doSetBreakpoint = aCallback => {
- let root = this._client.mainRoot;
- let location = {
- line: line,
- column: column
- };
- let packet = {
- to: this.actor,
- type: "setBreakpoint",
- location: location,
- condition: condition,
- noSliding: noSliding
- };
- // Backwards compatibility: send the breakpoint request to the
- // thread if the server doesn't support Debugger.Source actors.
- if (!root.traits.debuggerSourceActors) {
- packet.to = this._activeThread.actor;
- packet.location.url = this.url;
- }
- return this._client.request(packet).then(aResponse => {
- // Ignoring errors, since the user may be setting a breakpoint in a
- // dead script that will reappear on a page reload.
- let bpClient;
- if (aResponse.actor) {
- bpClient = new BreakpointClient(
- this._client,
- this,
- aResponse.actor,
- location,
- root.traits.conditionalBreakpoints ? condition : undefined
- );
- }
- aOnResponse(aResponse, bpClient);
- if (aCallback) {
- aCallback();
- }
- return [aResponse, bpClient];
- });
- };
- // If the debuggee is paused, just set the breakpoint.
- if (this._activeThread.paused) {
- return doSetBreakpoint();
- }
- // Otherwise, force a pause in order to set the breakpoint.
- return this._activeThread.interrupt().then(aResponse => {
- if (aResponse.error) {
- // Can't set the breakpoint if pausing failed.
- aOnResponse(aResponse);
- return aResponse;
- }
- const { type, why } = aResponse;
- const cleanUp = type == "paused" && why.type == "interrupted"
- ? () => this._activeThread.resume()
- : noop;
- return doSetBreakpoint(cleanUp);
- });
- }
- };
- /**
- * Breakpoint clients are used to remove breakpoints that are no longer used.
- *
- * @param aClient DebuggerClient
- * The debugger client parent.
- * @param aSourceClient SourceClient
- * The source where this breakpoint exists
- * @param aActor string
- * The actor ID for this breakpoint.
- * @param aLocation object
- * The location of the breakpoint. This is an object with two properties:
- * url and line.
- * @param aCondition string
- * The conditional expression of the breakpoint
- */
- function BreakpointClient(aClient, aSourceClient, aActor, aLocation, aCondition) {
- this._client = aClient;
- this._actor = aActor;
- this.location = aLocation;
- this.location.actor = aSourceClient.actor;
- this.location.url = aSourceClient.url;
- this.source = aSourceClient;
- this.request = this._client.request;
- // The condition property should only exist if it's a truthy value
- if (aCondition) {
- this.condition = aCondition;
- }
- }
- BreakpointClient.prototype = {
- _actor: null,
- get actor() { return this._actor; },
- get _transport() { return this._client._transport; },
- /**
- * Remove the breakpoint from the server.
- */
- remove: DebuggerClient.requester({
- type: "delete"
- }),
- /**
- * Determines if this breakpoint has a condition
- */
- hasCondition: function () {
- let root = this._client.mainRoot;
- // XXX bug 990137: We will remove support for client-side handling of
- // conditional breakpoints
- if (root.traits.conditionalBreakpoints) {
- return "condition" in this;
- } else {
- return "conditionalExpression" in this;
- }
- },
- /**
- * Get the condition of this breakpoint. Currently we have to
- * support locally emulated conditional breakpoints until the
- * debugger servers are updated (see bug 990137). We used a
- * different property when moving it server-side to ensure that we
- * are testing the right code.
- */
- getCondition: function () {
- let root = this._client.mainRoot;
- if (root.traits.conditionalBreakpoints) {
- return this.condition;
- } else {
- return this.conditionalExpression;
- }
- },
- /**
- * Set the condition of this breakpoint
- */
- setCondition: function (gThreadClient, aCondition) {
- let root = this._client.mainRoot;
- let deferred = promise.defer();
- if (root.traits.conditionalBreakpoints) {
- let info = {
- line: this.location.line,
- column: this.location.column,
- condition: aCondition
- };
- // Remove the current breakpoint and add a new one with the
- // condition.
- this.remove(aResponse => {
- if (aResponse && aResponse.error) {
- deferred.reject(aResponse);
- return;
- }
- this.source.setBreakpoint(info, (aResponse, aNewBreakpoint) => {
- if (aResponse && aResponse.error) {
- deferred.reject(aResponse);
- } else {
- deferred.resolve(aNewBreakpoint);
- }
- });
- });
- } else {
- // The property shouldn't even exist if the condition is blank
- if (aCondition === "") {
- delete this.conditionalExpression;
- }
- else {
- this.conditionalExpression = aCondition;
- }
- deferred.resolve(this);
- }
- return deferred.promise;
- }
- };
- eventSource(BreakpointClient.prototype);
- /**
- * Environment clients are used to manipulate the lexical environment actors.
- *
- * @param aClient DebuggerClient
- * The debugger client parent.
- * @param aForm Object
- * The form sent across the remote debugging protocol.
- */
- function EnvironmentClient(aClient, aForm) {
- this._client = aClient;
- this._form = aForm;
- this.request = this._client.request;
- }
- exports.EnvironmentClient = EnvironmentClient;
- EnvironmentClient.prototype = {
- get actor() {
- return this._form.actor;
- },
- get _transport() { return this._client._transport; },
- /**
- * Fetches the bindings introduced by this lexical environment.
- */
- getBindings: DebuggerClient.requester({
- type: "bindings"
- }),
- /**
- * Changes the value of the identifier whose name is name (a string) to that
- * represented by value (a grip).
- */
- assign: DebuggerClient.requester({
- type: "assign",
- name: args(0),
- value: args(1)
- })
- };
- eventSource(EnvironmentClient.prototype);
|