script.js 74 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this
  4. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. "use strict";
  6. const Services = require("Services");
  7. const { Cc, Ci, Cu, Cr, components, ChromeWorker } = require("chrome");
  8. const { ActorPool, OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
  9. const { BreakpointActor, setBreakpointAtEntryPoints } = require("devtools/server/actors/breakpoint");
  10. const { EnvironmentActor } = require("devtools/server/actors/environment");
  11. const { FrameActor } = require("devtools/server/actors/frame");
  12. const { ObjectActor, createValueGrip, longStringGrip } = require("devtools/server/actors/object");
  13. const { SourceActor, getSourceURL } = require("devtools/server/actors/source");
  14. const { DebuggerServer } = require("devtools/server/main");
  15. const { ActorClassWithSpec } = require("devtools/shared/protocol");
  16. const DevToolsUtils = require("devtools/shared/DevToolsUtils");
  17. const flags = require("devtools/shared/flags");
  18. const { assert, dumpn, update, fetch } = DevToolsUtils;
  19. const promise = require("promise");
  20. const xpcInspector = require("xpcInspector");
  21. const { DevToolsWorker } = require("devtools/shared/worker/worker");
  22. const object = require("sdk/util/object");
  23. const { threadSpec } = require("devtools/shared/specs/script");
  24. const { defer, resolve, reject, all } = promise;
  25. loader.lazyGetter(this, "Debugger", () => {
  26. let Debugger = require("Debugger");
  27. hackDebugger(Debugger);
  28. return Debugger;
  29. });
  30. loader.lazyRequireGetter(this, "CssLogic", "devtools/server/css-logic", true);
  31. loader.lazyRequireGetter(this, "events", "sdk/event/core");
  32. loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
  33. /**
  34. * A BreakpointActorMap is a map from locations to instances of BreakpointActor.
  35. */
  36. function BreakpointActorMap() {
  37. this._size = 0;
  38. this._actors = {};
  39. }
  40. BreakpointActorMap.prototype = {
  41. /**
  42. * Return the number of BreakpointActors in this BreakpointActorMap.
  43. *
  44. * @returns Number
  45. * The number of BreakpointActor in this BreakpointActorMap.
  46. */
  47. get size() {
  48. return this._size;
  49. },
  50. /**
  51. * Generate all BreakpointActors that match the given location in
  52. * this BreakpointActorMap.
  53. *
  54. * @param OriginalLocation location
  55. * The location for which matching BreakpointActors should be generated.
  56. */
  57. findActors: function* (location = new OriginalLocation()) {
  58. // Fast shortcut for when we know we won't find any actors. Surprisingly
  59. // enough, this speeds up refreshing when there are no breakpoints set by
  60. // about 2x!
  61. if (this.size === 0) {
  62. return;
  63. }
  64. function* findKeys(object, key) {
  65. if (key !== undefined) {
  66. if (key in object) {
  67. yield key;
  68. }
  69. }
  70. else {
  71. for (let key of Object.keys(object)) {
  72. yield key;
  73. }
  74. }
  75. }
  76. let query = {
  77. sourceActorID: location.originalSourceActor ? location.originalSourceActor.actorID : undefined,
  78. line: location.originalLine,
  79. };
  80. // If location contains a line, assume we are searching for a whole line
  81. // breakpoint, and set begin/endColumn accordingly. Otherwise, we are
  82. // searching for all breakpoints, so begin/endColumn should be left unset.
  83. if (location.originalLine) {
  84. query.beginColumn = location.originalColumn ? location.originalColumn : 0;
  85. query.endColumn = location.originalColumn ? location.originalColumn + 1 : Infinity;
  86. } else {
  87. query.beginColumn = location.originalColumn ? query.originalColumn : undefined;
  88. query.endColumn = location.originalColumn ? query.originalColumn + 1 : undefined;
  89. }
  90. for (let sourceActorID of findKeys(this._actors, query.sourceActorID))
  91. for (let line of findKeys(this._actors[sourceActorID], query.line))
  92. for (let beginColumn of findKeys(this._actors[sourceActorID][line], query.beginColumn))
  93. for (let endColumn of findKeys(this._actors[sourceActorID][line][beginColumn], query.endColumn)) {
  94. yield this._actors[sourceActorID][line][beginColumn][endColumn];
  95. }
  96. },
  97. /**
  98. * Return the BreakpointActor at the given location in this
  99. * BreakpointActorMap.
  100. *
  101. * @param OriginalLocation location
  102. * The location for which the BreakpointActor should be returned.
  103. *
  104. * @returns BreakpointActor actor
  105. * The BreakpointActor at the given location.
  106. */
  107. getActor: function (originalLocation) {
  108. for (let actor of this.findActors(originalLocation)) {
  109. return actor;
  110. }
  111. return null;
  112. },
  113. /**
  114. * Set the given BreakpointActor to the given location in this
  115. * BreakpointActorMap.
  116. *
  117. * @param OriginalLocation location
  118. * The location to which the given BreakpointActor should be set.
  119. *
  120. * @param BreakpointActor actor
  121. * The BreakpointActor to be set to the given location.
  122. */
  123. setActor: function (location, actor) {
  124. let { originalSourceActor, originalLine, originalColumn } = location;
  125. let sourceActorID = originalSourceActor.actorID;
  126. let line = originalLine;
  127. let beginColumn = originalColumn ? originalColumn : 0;
  128. let endColumn = originalColumn ? originalColumn + 1 : Infinity;
  129. if (!this._actors[sourceActorID]) {
  130. this._actors[sourceActorID] = [];
  131. }
  132. if (!this._actors[sourceActorID][line]) {
  133. this._actors[sourceActorID][line] = [];
  134. }
  135. if (!this._actors[sourceActorID][line][beginColumn]) {
  136. this._actors[sourceActorID][line][beginColumn] = [];
  137. }
  138. if (!this._actors[sourceActorID][line][beginColumn][endColumn]) {
  139. ++this._size;
  140. }
  141. this._actors[sourceActorID][line][beginColumn][endColumn] = actor;
  142. },
  143. /**
  144. * Delete the BreakpointActor from the given location in this
  145. * BreakpointActorMap.
  146. *
  147. * @param OriginalLocation location
  148. * The location from which the BreakpointActor should be deleted.
  149. */
  150. deleteActor: function (location) {
  151. let { originalSourceActor, originalLine, originalColumn } = location;
  152. let sourceActorID = originalSourceActor.actorID;
  153. let line = originalLine;
  154. let beginColumn = originalColumn ? originalColumn : 0;
  155. let endColumn = originalColumn ? originalColumn + 1 : Infinity;
  156. if (this._actors[sourceActorID]) {
  157. if (this._actors[sourceActorID][line]) {
  158. if (this._actors[sourceActorID][line][beginColumn]) {
  159. if (this._actors[sourceActorID][line][beginColumn][endColumn]) {
  160. --this._size;
  161. }
  162. delete this._actors[sourceActorID][line][beginColumn][endColumn];
  163. if (Object.keys(this._actors[sourceActorID][line][beginColumn]).length === 0) {
  164. delete this._actors[sourceActorID][line][beginColumn];
  165. }
  166. }
  167. if (Object.keys(this._actors[sourceActorID][line]).length === 0) {
  168. delete this._actors[sourceActorID][line];
  169. }
  170. }
  171. }
  172. }
  173. };
  174. exports.BreakpointActorMap = BreakpointActorMap;
  175. /**
  176. * Keeps track of persistent sources across reloads and ties different
  177. * source instances to the same actor id so that things like
  178. * breakpoints survive reloads. ThreadSources uses this to force the
  179. * same actorID on a SourceActor.
  180. */
  181. function SourceActorStore() {
  182. // source identifier --> actor id
  183. this._sourceActorIds = Object.create(null);
  184. }
  185. SourceActorStore.prototype = {
  186. /**
  187. * Lookup an existing actor id that represents this source, if available.
  188. */
  189. getReusableActorId: function (aSource, aOriginalUrl) {
  190. let url = this.getUniqueKey(aSource, aOriginalUrl);
  191. if (url && url in this._sourceActorIds) {
  192. return this._sourceActorIds[url];
  193. }
  194. return null;
  195. },
  196. /**
  197. * Update a source with an actorID.
  198. */
  199. setReusableActorId: function (aSource, aOriginalUrl, actorID) {
  200. let url = this.getUniqueKey(aSource, aOriginalUrl);
  201. if (url) {
  202. this._sourceActorIds[url] = actorID;
  203. }
  204. },
  205. /**
  206. * Make a unique URL from a source that identifies it across reloads.
  207. */
  208. getUniqueKey: function (aSource, aOriginalUrl) {
  209. if (aOriginalUrl) {
  210. // Original source from a sourcemap.
  211. return aOriginalUrl;
  212. }
  213. else {
  214. return getSourceURL(aSource);
  215. }
  216. }
  217. };
  218. exports.SourceActorStore = SourceActorStore;
  219. /**
  220. * Manages pushing event loops and automatically pops and exits them in the
  221. * correct order as they are resolved.
  222. *
  223. * @param ThreadActor thread
  224. * The thread actor instance that owns this EventLoopStack.
  225. * @param DebuggerServerConnection connection
  226. * The remote protocol connection associated with this event loop stack.
  227. * @param Object hooks
  228. * An object with the following properties:
  229. * - url: The URL string of the debuggee we are spinning an event loop
  230. * for.
  231. * - preNest: function called before entering a nested event loop
  232. * - postNest: function called after exiting a nested event loop
  233. */
  234. function EventLoopStack({ thread, connection, hooks }) {
  235. this._hooks = hooks;
  236. this._thread = thread;
  237. this._connection = connection;
  238. }
  239. EventLoopStack.prototype = {
  240. /**
  241. * The number of nested event loops on the stack.
  242. */
  243. get size() {
  244. return xpcInspector.eventLoopNestLevel;
  245. },
  246. /**
  247. * The URL of the debuggee who pushed the event loop on top of the stack.
  248. */
  249. get lastPausedUrl() {
  250. let url = null;
  251. if (this.size > 0) {
  252. try {
  253. url = xpcInspector.lastNestRequestor.url;
  254. } catch (e) {
  255. // The tab's URL getter may throw if the tab is destroyed by the time
  256. // this code runs, but we don't really care at this point.
  257. dumpn(e);
  258. }
  259. }
  260. return url;
  261. },
  262. /**
  263. * The DebuggerServerConnection of the debugger who pushed the event loop on
  264. * top of the stack
  265. */
  266. get lastConnection() {
  267. return xpcInspector.lastNestRequestor._connection;
  268. },
  269. /**
  270. * Push a new nested event loop onto the stack.
  271. *
  272. * @returns EventLoop
  273. */
  274. push: function () {
  275. return new EventLoop({
  276. thread: this._thread,
  277. connection: this._connection,
  278. hooks: this._hooks
  279. });
  280. }
  281. };
  282. /**
  283. * An object that represents a nested event loop. It is used as the nest
  284. * requestor with nsIJSInspector instances.
  285. *
  286. * @param ThreadActor thread
  287. * The thread actor that is creating this nested event loop.
  288. * @param DebuggerServerConnection connection
  289. * The remote protocol connection associated with this event loop.
  290. * @param Object hooks
  291. * The same hooks object passed into EventLoopStack during its
  292. * initialization.
  293. */
  294. function EventLoop({ thread, connection, hooks }) {
  295. this._thread = thread;
  296. this._hooks = hooks;
  297. this._connection = connection;
  298. this.enter = this.enter.bind(this);
  299. this.resolve = this.resolve.bind(this);
  300. }
  301. EventLoop.prototype = {
  302. entered: false,
  303. resolved: false,
  304. get url() { return this._hooks.url; },
  305. /**
  306. * Enter this nested event loop.
  307. */
  308. enter: function () {
  309. let nestData = this._hooks.preNest
  310. ? this._hooks.preNest()
  311. : null;
  312. this.entered = true;
  313. xpcInspector.enterNestedEventLoop(this);
  314. // Keep exiting nested event loops while the last requestor is resolved.
  315. if (xpcInspector.eventLoopNestLevel > 0) {
  316. const { resolved } = xpcInspector.lastNestRequestor;
  317. if (resolved) {
  318. xpcInspector.exitNestedEventLoop();
  319. }
  320. }
  321. if (this._hooks.postNest) {
  322. this._hooks.postNest(nestData);
  323. }
  324. },
  325. /**
  326. * Resolve this nested event loop.
  327. *
  328. * @returns boolean
  329. * True if we exited this nested event loop because it was on top of
  330. * the stack, false if there is another nested event loop above this
  331. * one that hasn't resolved yet.
  332. */
  333. resolve: function () {
  334. if (!this.entered) {
  335. throw new Error("Can't resolve an event loop before it has been entered!");
  336. }
  337. if (this.resolved) {
  338. throw new Error("Already resolved this nested event loop!");
  339. }
  340. this.resolved = true;
  341. if (this === xpcInspector.lastNestRequestor) {
  342. xpcInspector.exitNestedEventLoop();
  343. return true;
  344. }
  345. return false;
  346. },
  347. };
  348. /**
  349. * JSD2 actors.
  350. */
  351. /**
  352. * Creates a ThreadActor.
  353. *
  354. * ThreadActors manage a JSInspector object and manage execution/inspection
  355. * of debuggees.
  356. *
  357. * @param aParent object
  358. * This |ThreadActor|'s parent actor. It must implement the following
  359. * properties:
  360. * - url: The URL string of the debuggee.
  361. * - window: The global window object.
  362. * - preNest: Function called before entering a nested event loop.
  363. * - postNest: Function called after exiting a nested event loop.
  364. * - makeDebugger: A function that takes no arguments and instantiates
  365. * a Debugger that manages its globals on its own.
  366. * @param aGlobal object [optional]
  367. * An optional (for content debugging only) reference to the content
  368. * window.
  369. */
  370. const ThreadActor = ActorClassWithSpec(threadSpec, {
  371. initialize: function (aParent, aGlobal) {
  372. this._state = "detached";
  373. this._frameActors = [];
  374. this._parent = aParent;
  375. this._dbg = null;
  376. this._gripDepth = 0;
  377. this._threadLifetimePool = null;
  378. this._tabClosed = false;
  379. this._scripts = null;
  380. this._pauseOnDOMEvents = null;
  381. this._options = {
  382. useSourceMaps: false,
  383. autoBlackBox: false
  384. };
  385. this.breakpointActorMap = new BreakpointActorMap();
  386. this.sourceActorStore = new SourceActorStore();
  387. this._debuggerSourcesSeen = null;
  388. // A map of actorID -> actor for breakpoints created and managed by the
  389. // server.
  390. this._hiddenBreakpoints = new Map();
  391. this.global = aGlobal;
  392. this._allEventsListener = this._allEventsListener.bind(this);
  393. this.onNewGlobal = this.onNewGlobal.bind(this);
  394. this.onSourceEvent = this.onSourceEvent.bind(this);
  395. this.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
  396. this.onDebuggerStatement = this.onDebuggerStatement.bind(this);
  397. this.onNewScript = this.onNewScript.bind(this);
  398. this.objectGrip = this.objectGrip.bind(this);
  399. this.pauseObjectGrip = this.pauseObjectGrip.bind(this);
  400. this._onWindowReady = this._onWindowReady.bind(this);
  401. events.on(this._parent, "window-ready", this._onWindowReady);
  402. // Set a wrappedJSObject property so |this| can be sent via the observer svc
  403. // for the xpcshell harness.
  404. this.wrappedJSObject = this;
  405. },
  406. // Used by the ObjectActor to keep track of the depth of grip() calls.
  407. _gripDepth: null,
  408. get dbg() {
  409. if (!this._dbg) {
  410. this._dbg = this._parent.makeDebugger();
  411. this._dbg.uncaughtExceptionHook = this.uncaughtExceptionHook;
  412. this._dbg.onDebuggerStatement = this.onDebuggerStatement;
  413. this._dbg.onNewScript = this.onNewScript;
  414. this._dbg.on("newGlobal", this.onNewGlobal);
  415. // Keep the debugger disabled until a client attaches.
  416. this._dbg.enabled = this._state != "detached";
  417. }
  418. return this._dbg;
  419. },
  420. get globalDebugObject() {
  421. if (!this._parent.window) {
  422. return null;
  423. }
  424. return this.dbg.makeGlobalObjectReference(this._parent.window);
  425. },
  426. get state() {
  427. return this._state;
  428. },
  429. get attached() {
  430. return this.state == "attached" ||
  431. this.state == "running" ||
  432. this.state == "paused";
  433. },
  434. get threadLifetimePool() {
  435. if (!this._threadLifetimePool) {
  436. this._threadLifetimePool = new ActorPool(this.conn);
  437. this.conn.addActorPool(this._threadLifetimePool);
  438. this._threadLifetimePool.objectActors = new WeakMap();
  439. }
  440. return this._threadLifetimePool;
  441. },
  442. get sources() {
  443. return this._parent.sources;
  444. },
  445. get youngestFrame() {
  446. if (this.state != "paused") {
  447. return null;
  448. }
  449. return this.dbg.getNewestFrame();
  450. },
  451. _prettyPrintWorker: null,
  452. get prettyPrintWorker() {
  453. if (!this._prettyPrintWorker) {
  454. this._prettyPrintWorker = new DevToolsWorker(
  455. "resource://devtools/server/actors/pretty-print-worker.js",
  456. { name: "pretty-print",
  457. verbose: flags.wantLogging }
  458. );
  459. }
  460. return this._prettyPrintWorker;
  461. },
  462. /**
  463. * Keep track of all of the nested event loops we use to pause the debuggee
  464. * when we hit a breakpoint/debugger statement/etc in one place so we can
  465. * resolve them when we get resume packets. We have more than one (and keep
  466. * them in a stack) because we can pause within client evals.
  467. */
  468. _threadPauseEventLoops: null,
  469. _pushThreadPause: function () {
  470. if (!this._threadPauseEventLoops) {
  471. this._threadPauseEventLoops = [];
  472. }
  473. const eventLoop = this._nestedEventLoops.push();
  474. this._threadPauseEventLoops.push(eventLoop);
  475. eventLoop.enter();
  476. },
  477. _popThreadPause: function () {
  478. const eventLoop = this._threadPauseEventLoops.pop();
  479. assert(eventLoop, "Should have an event loop.");
  480. eventLoop.resolve();
  481. },
  482. /**
  483. * Remove all debuggees and clear out the thread's sources.
  484. */
  485. clearDebuggees: function () {
  486. if (this._dbg) {
  487. this.dbg.removeAllDebuggees();
  488. }
  489. this._sources = null;
  490. this._scripts = null;
  491. },
  492. /**
  493. * Listener for our |Debugger|'s "newGlobal" event.
  494. */
  495. onNewGlobal: function (aGlobal) {
  496. // Notify the client.
  497. this.conn.send({
  498. from: this.actorID,
  499. type: "newGlobal",
  500. // TODO: after bug 801084 lands see if we need to JSONify this.
  501. hostAnnotations: aGlobal.hostAnnotations
  502. });
  503. },
  504. disconnect: function () {
  505. dumpn("in ThreadActor.prototype.disconnect");
  506. if (this._state == "paused") {
  507. this.onResume();
  508. }
  509. // Blow away our source actor ID store because those IDs are only
  510. // valid for this connection. This is ok because we never keep
  511. // things like breakpoints across connections.
  512. this._sourceActorStore = null;
  513. events.off(this._parent, "window-ready", this._onWindowReady);
  514. this.sources.off("newSource", this.onSourceEvent);
  515. this.sources.off("updatedSource", this.onSourceEvent);
  516. this.clearDebuggees();
  517. this.conn.removeActorPool(this._threadLifetimePool);
  518. this._threadLifetimePool = null;
  519. if (this._prettyPrintWorker) {
  520. this._prettyPrintWorker.destroy();
  521. this._prettyPrintWorker = null;
  522. }
  523. if (!this._dbg) {
  524. return;
  525. }
  526. this._dbg.enabled = false;
  527. this._dbg = null;
  528. },
  529. /**
  530. * Disconnect the debugger and put the actor in the exited state.
  531. */
  532. exit: function () {
  533. this.disconnect();
  534. this._state = "exited";
  535. },
  536. // Request handlers
  537. onAttach: function (aRequest) {
  538. if (this.state === "exited") {
  539. return { type: "exited" };
  540. }
  541. if (this.state !== "detached") {
  542. return { error: "wrongState",
  543. message: "Current state is " + this.state };
  544. }
  545. this._state = "attached";
  546. this._debuggerSourcesSeen = new WeakSet();
  547. Object.assign(this._options, aRequest.options || {});
  548. this.sources.setOptions(this._options);
  549. this.sources.on("newSource", this.onSourceEvent);
  550. this.sources.on("updatedSource", this.onSourceEvent);
  551. // Initialize an event loop stack. This can't be done in the constructor,
  552. // because this.conn is not yet initialized by the actor pool at that time.
  553. this._nestedEventLoops = new EventLoopStack({
  554. hooks: this._parent,
  555. connection: this.conn,
  556. thread: this
  557. });
  558. this.dbg.addDebuggees();
  559. this.dbg.enabled = true;
  560. try {
  561. // Put ourselves in the paused state.
  562. let packet = this._paused();
  563. if (!packet) {
  564. return { error: "notAttached" };
  565. }
  566. packet.why = { type: "attached" };
  567. // Send the response to the attach request now (rather than
  568. // returning it), because we're going to start a nested event loop
  569. // here.
  570. this.conn.send(packet);
  571. // Start a nested event loop.
  572. this._pushThreadPause();
  573. // We already sent a response to this request, don't send one
  574. // now.
  575. return null;
  576. } catch (e) {
  577. reportError(e);
  578. return { error: "notAttached", message: e.toString() };
  579. }
  580. },
  581. onDetach: function (aRequest) {
  582. this.disconnect();
  583. this._state = "detached";
  584. this._debuggerSourcesSeen = null;
  585. dumpn("ThreadActor.prototype.onDetach: returning 'detached' packet");
  586. return {
  587. type: "detached"
  588. };
  589. },
  590. onReconfigure: function (aRequest) {
  591. if (this.state == "exited") {
  592. return { error: "wrongState" };
  593. }
  594. const options = aRequest.options || {};
  595. if ("observeAsmJS" in options) {
  596. this.dbg.allowUnobservedAsmJS = !options.observeAsmJS;
  597. }
  598. Object.assign(this._options, options);
  599. // Update the global source store
  600. this.sources.setOptions(options);
  601. return {};
  602. },
  603. /**
  604. * Pause the debuggee, by entering a nested event loop, and return a 'paused'
  605. * packet to the client.
  606. *
  607. * @param Debugger.Frame aFrame
  608. * The newest debuggee frame in the stack.
  609. * @param object aReason
  610. * An object with a 'type' property containing the reason for the pause.
  611. * @param function onPacket
  612. * Hook to modify the packet before it is sent. Feel free to return a
  613. * promise.
  614. */
  615. _pauseAndRespond: function (aFrame, aReason, onPacket = function (k) { return k; }) {
  616. try {
  617. let packet = this._paused(aFrame);
  618. if (!packet) {
  619. return undefined;
  620. }
  621. packet.why = aReason;
  622. let generatedLocation = this.sources.getFrameLocation(aFrame);
  623. this.sources.getOriginalLocation(generatedLocation)
  624. .then((originalLocation) => {
  625. if (!originalLocation.originalSourceActor) {
  626. // The only time the source actor will be null is if there
  627. // was a sourcemap and it tried to look up the original
  628. // location but there was no original URL. This is a strange
  629. // scenario so we simply don't pause.
  630. DevToolsUtils.reportException(
  631. "ThreadActor",
  632. new Error("Attempted to pause in a script with a sourcemap but " +
  633. "could not find original location.")
  634. );
  635. return undefined;
  636. }
  637. packet.frame.where = {
  638. source: originalLocation.originalSourceActor.form(),
  639. line: originalLocation.originalLine,
  640. column: originalLocation.originalColumn
  641. };
  642. resolve(onPacket(packet))
  643. .then(null, error => {
  644. reportError(error);
  645. return {
  646. error: "unknownError",
  647. message: error.message + "\n" + error.stack
  648. };
  649. })
  650. .then(packet => {
  651. this.conn.send(packet);
  652. });
  653. });
  654. this._pushThreadPause();
  655. } catch (e) {
  656. reportError(e, "Got an exception during TA__pauseAndRespond: ");
  657. }
  658. // If the browser tab has been closed, terminate the debuggee script
  659. // instead of continuing. Executing JS after the content window is gone is
  660. // a bad idea.
  661. return this._tabClosed ? null : undefined;
  662. },
  663. _makeOnEnterFrame: function ({ pauseAndRespond }) {
  664. return aFrame => {
  665. const generatedLocation = this.sources.getFrameLocation(aFrame);
  666. let { originalSourceActor } = this.unsafeSynchronize(this.sources.getOriginalLocation(
  667. generatedLocation));
  668. let url = originalSourceActor.url;
  669. return this.sources.isBlackBoxed(url)
  670. ? undefined
  671. : pauseAndRespond(aFrame);
  672. };
  673. },
  674. _makeOnPop: function ({ thread, pauseAndRespond, createValueGrip }) {
  675. return function (aCompletion) {
  676. // onPop is called with 'this' set to the current frame.
  677. const generatedLocation = thread.sources.getFrameLocation(this);
  678. const { originalSourceActor } = thread.unsafeSynchronize(thread.sources.getOriginalLocation(
  679. generatedLocation));
  680. const url = originalSourceActor.url;
  681. if (thread.sources.isBlackBoxed(url)) {
  682. return undefined;
  683. }
  684. // Note that we're popping this frame; we need to watch for
  685. // subsequent step events on its caller.
  686. this.reportedPop = true;
  687. return pauseAndRespond(this, aPacket => {
  688. aPacket.why.frameFinished = {};
  689. if (!aCompletion) {
  690. aPacket.why.frameFinished.terminated = true;
  691. } else if (aCompletion.hasOwnProperty("return")) {
  692. aPacket.why.frameFinished.return = createValueGrip(aCompletion.return);
  693. } else if (aCompletion.hasOwnProperty("yield")) {
  694. aPacket.why.frameFinished.return = createValueGrip(aCompletion.yield);
  695. } else {
  696. aPacket.why.frameFinished.throw = createValueGrip(aCompletion.throw);
  697. }
  698. return aPacket;
  699. });
  700. };
  701. },
  702. _makeOnStep: function ({ thread, pauseAndRespond, startFrame,
  703. startLocation, steppingType }) {
  704. // Breaking in place: we should always pause.
  705. if (steppingType === "break") {
  706. return function () {
  707. return pauseAndRespond(this);
  708. };
  709. }
  710. // Otherwise take what a "step" means into consideration.
  711. return function () {
  712. // onStep is called with 'this' set to the current frame.
  713. // Only allow stepping stops at entry points for the line, when
  714. // the stepping occurs in a single frame. The "same frame"
  715. // check makes it so a sequence of steps can step out of a frame
  716. // and into subsequent calls in the outer frame. E.g., if there
  717. // is a call "a(b())" and the user steps into b, then this
  718. // condition makes it possible to step out of b and into a.
  719. if (this === startFrame &&
  720. !this.script.getOffsetLocation(this.offset).isEntryPoint) {
  721. return undefined;
  722. }
  723. const generatedLocation = thread.sources.getFrameLocation(this);
  724. const newLocation = thread.unsafeSynchronize(thread.sources.getOriginalLocation(
  725. generatedLocation));
  726. // Cases when we should pause because we have executed enough to consider
  727. // a "step" to have occured:
  728. //
  729. // 1.1. We change frames.
  730. // 1.2. We change URLs (can happen without changing frames thanks to
  731. // source mapping).
  732. // 1.3. We change lines.
  733. //
  734. // Cases when we should always continue execution, even if one of the
  735. // above cases is true:
  736. //
  737. // 2.1. We are in a source mapped region, but inside a null mapping
  738. // (doesn't correlate to any region of original source)
  739. // 2.2. The source we are in is black boxed.
  740. // Cases 2.1 and 2.2
  741. if (newLocation.originalUrl == null
  742. || thread.sources.isBlackBoxed(newLocation.originalUrl)) {
  743. return undefined;
  744. }
  745. // Cases 1.1, 1.2 and 1.3
  746. if (this !== startFrame
  747. || startLocation.originalUrl !== newLocation.originalUrl
  748. || startLocation.originalLine !== newLocation.originalLine) {
  749. return pauseAndRespond(this);
  750. }
  751. // Otherwise, let execution continue (we haven't executed enough code to
  752. // consider this a "step" yet).
  753. return undefined;
  754. };
  755. },
  756. /**
  757. * Define the JS hook functions for stepping.
  758. */
  759. _makeSteppingHooks: function (aStartLocation, steppingType) {
  760. // Bind these methods and state because some of the hooks are called
  761. // with 'this' set to the current frame. Rather than repeating the
  762. // binding in each _makeOnX method, just do it once here and pass it
  763. // in to each function.
  764. const steppingHookState = {
  765. pauseAndRespond: (aFrame, onPacket = k=>k) => {
  766. return this._pauseAndRespond(aFrame, { type: "resumeLimit" }, onPacket);
  767. },
  768. createValueGrip: v => createValueGrip(v, this._pausePool,
  769. this.objectGrip),
  770. thread: this,
  771. startFrame: this.youngestFrame,
  772. startLocation: aStartLocation,
  773. steppingType: steppingType
  774. };
  775. return {
  776. onEnterFrame: this._makeOnEnterFrame(steppingHookState),
  777. onPop: this._makeOnPop(steppingHookState),
  778. onStep: this._makeOnStep(steppingHookState)
  779. };
  780. },
  781. /**
  782. * Handle attaching the various stepping hooks we need to attach when we
  783. * receive a resume request with a resumeLimit property.
  784. *
  785. * @param Object aRequest
  786. * The request packet received over the RDP.
  787. * @returns A promise that resolves to true once the hooks are attached, or is
  788. * rejected with an error packet.
  789. */
  790. _handleResumeLimit: function (aRequest) {
  791. let steppingType = aRequest.resumeLimit.type;
  792. if (["break", "step", "next", "finish"].indexOf(steppingType) == -1) {
  793. return reject({ error: "badParameterType",
  794. message: "Unknown resumeLimit type" });
  795. }
  796. const generatedLocation = this.sources.getFrameLocation(this.youngestFrame);
  797. return this.sources.getOriginalLocation(generatedLocation)
  798. .then(originalLocation => {
  799. const { onEnterFrame, onPop, onStep } = this._makeSteppingHooks(originalLocation,
  800. steppingType);
  801. // Make sure there is still a frame on the stack if we are to continue
  802. // stepping.
  803. let stepFrame = this._getNextStepFrame(this.youngestFrame);
  804. if (stepFrame) {
  805. switch (steppingType) {
  806. case "step":
  807. this.dbg.onEnterFrame = onEnterFrame;
  808. // Fall through.
  809. case "break":
  810. case "next":
  811. if (stepFrame.script) {
  812. stepFrame.onStep = onStep;
  813. }
  814. stepFrame.onPop = onPop;
  815. break;
  816. case "finish":
  817. stepFrame.onPop = onPop;
  818. }
  819. }
  820. return true;
  821. });
  822. },
  823. /**
  824. * Clear the onStep and onPop hooks from the given frame and all of the frames
  825. * below it.
  826. *
  827. * @param Debugger.Frame aFrame
  828. * The frame we want to clear the stepping hooks from.
  829. */
  830. _clearSteppingHooks: function (aFrame) {
  831. if (aFrame && aFrame.live) {
  832. while (aFrame) {
  833. aFrame.onStep = undefined;
  834. aFrame.onPop = undefined;
  835. aFrame = aFrame.older;
  836. }
  837. }
  838. },
  839. /**
  840. * Listen to the debuggee's DOM events if we received a request to do so.
  841. *
  842. * @param Object aRequest
  843. * The resume request packet received over the RDP.
  844. */
  845. _maybeListenToEvents: function (aRequest) {
  846. // Break-on-DOMEvents is only supported in content debugging.
  847. let events = aRequest.pauseOnDOMEvents;
  848. if (this.global && events &&
  849. (events == "*" ||
  850. (Array.isArray(events) && events.length))) {
  851. this._pauseOnDOMEvents = events;
  852. let els = Cc["@mozilla.org/eventlistenerservice;1"]
  853. .getService(Ci.nsIEventListenerService);
  854. els.addListenerForAllEvents(this.global, this._allEventsListener, true);
  855. }
  856. },
  857. /**
  858. * If we are tasked with breaking on the load event, we have to add the
  859. * listener early enough.
  860. */
  861. _onWindowReady: function () {
  862. this._maybeListenToEvents({
  863. pauseOnDOMEvents: this._pauseOnDOMEvents
  864. });
  865. },
  866. /**
  867. * Handle a protocol request to resume execution of the debuggee.
  868. */
  869. onResume: function (aRequest) {
  870. if (this._state !== "paused") {
  871. return {
  872. error: "wrongState",
  873. message: "Can't resume when debuggee isn't paused. Current state is '"
  874. + this._state + "'",
  875. state: this._state
  876. };
  877. }
  878. // In case of multiple nested event loops (due to multiple debuggers open in
  879. // different tabs or multiple debugger clients connected to the same tab)
  880. // only allow resumption in a LIFO order.
  881. if (this._nestedEventLoops.size && this._nestedEventLoops.lastPausedUrl
  882. && (this._nestedEventLoops.lastPausedUrl !== this._parent.url
  883. || this._nestedEventLoops.lastConnection !== this.conn)) {
  884. return {
  885. error: "wrongOrder",
  886. message: "trying to resume in the wrong order.",
  887. lastPausedUrl: this._nestedEventLoops.lastPausedUrl
  888. };
  889. }
  890. let resumeLimitHandled;
  891. if (aRequest && aRequest.resumeLimit) {
  892. resumeLimitHandled = this._handleResumeLimit(aRequest);
  893. } else {
  894. this._clearSteppingHooks(this.youngestFrame);
  895. resumeLimitHandled = resolve(true);
  896. }
  897. return resumeLimitHandled.then(() => {
  898. if (aRequest) {
  899. this._options.pauseOnExceptions = aRequest.pauseOnExceptions;
  900. this._options.ignoreCaughtExceptions = aRequest.ignoreCaughtExceptions;
  901. this.maybePauseOnExceptions();
  902. this._maybeListenToEvents(aRequest);
  903. }
  904. let packet = this._resumed();
  905. this._popThreadPause();
  906. // Tell anyone who cares of the resume (as of now, that's the xpcshell
  907. // harness)
  908. if (Services.obs) {
  909. Services.obs.notifyObservers(this, "devtools-thread-resumed", null);
  910. }
  911. return packet;
  912. }, error => {
  913. return error instanceof Error
  914. ? { error: "unknownError",
  915. message: DevToolsUtils.safeErrorString(error) }
  916. // It is a known error, and the promise was rejected with an error
  917. // packet.
  918. : error;
  919. });
  920. },
  921. /**
  922. * Spin up a nested event loop so we can synchronously resolve a promise.
  923. *
  924. * DON'T USE THIS UNLESS YOU ABSOLUTELY MUST! Nested event loops suck: the
  925. * world's state can change out from underneath your feet because JS is no
  926. * longer run-to-completion.
  927. *
  928. * @param aPromise
  929. * The promise we want to resolve.
  930. * @returns The promise's resolution.
  931. */
  932. unsafeSynchronize: function (aPromise) {
  933. let needNest = true;
  934. let eventLoop;
  935. let returnVal;
  936. aPromise
  937. .then((aResolvedVal) => {
  938. needNest = false;
  939. returnVal = aResolvedVal;
  940. })
  941. .then(null, (aError) => {
  942. reportError(aError, "Error inside unsafeSynchronize:");
  943. })
  944. .then(() => {
  945. if (eventLoop) {
  946. eventLoop.resolve();
  947. }
  948. });
  949. if (needNest) {
  950. eventLoop = this._nestedEventLoops.push();
  951. eventLoop.enter();
  952. }
  953. return returnVal;
  954. },
  955. /**
  956. * Set the debugging hook to pause on exceptions if configured to do so.
  957. */
  958. maybePauseOnExceptions: function () {
  959. if (this._options.pauseOnExceptions) {
  960. this.dbg.onExceptionUnwind = this.onExceptionUnwind.bind(this);
  961. }
  962. },
  963. /**
  964. * A listener that gets called for every event fired on the page, when a list
  965. * of interesting events was provided with the pauseOnDOMEvents property. It
  966. * is used to set server-managed breakpoints on any existing event listeners
  967. * for those events.
  968. *
  969. * @param Event event
  970. * The event that was fired.
  971. */
  972. _allEventsListener: function (event) {
  973. if (this._pauseOnDOMEvents == "*" ||
  974. this._pauseOnDOMEvents.indexOf(event.type) != -1) {
  975. for (let listener of this._getAllEventListeners(event.target)) {
  976. if (event.type == listener.type || this._pauseOnDOMEvents == "*") {
  977. this._breakOnEnter(listener.script);
  978. }
  979. }
  980. }
  981. },
  982. /**
  983. * Return an array containing all the event listeners attached to the
  984. * specified event target and its ancestors in the event target chain.
  985. *
  986. * @param EventTarget eventTarget
  987. * The target the event was dispatched on.
  988. * @returns Array
  989. */
  990. _getAllEventListeners: function (eventTarget) {
  991. let els = Cc["@mozilla.org/eventlistenerservice;1"]
  992. .getService(Ci.nsIEventListenerService);
  993. let targets = els.getEventTargetChainFor(eventTarget, true);
  994. let listeners = [];
  995. for (let target of targets) {
  996. let handlers = els.getListenerInfoFor(target);
  997. for (let handler of handlers) {
  998. // Null is returned for all-events handlers, and native event listeners
  999. // don't provide any listenerObject, which makes them not that useful to
  1000. // a JS debugger.
  1001. if (!handler || !handler.listenerObject || !handler.type)
  1002. continue;
  1003. // Create a listener-like object suitable for our purposes.
  1004. let l = Object.create(null);
  1005. l.type = handler.type;
  1006. let listener = handler.listenerObject;
  1007. let listenerDO = this.globalDebugObject.makeDebuggeeValue(listener);
  1008. // If the listener is not callable, assume it is an event handler object.
  1009. if (!listenerDO.callable) {
  1010. // For some events we don't have permission to access the
  1011. // 'handleEvent' property when running in content scope.
  1012. if (!listenerDO.unwrap()) {
  1013. continue;
  1014. }
  1015. let heDesc;
  1016. while (!heDesc && listenerDO) {
  1017. heDesc = listenerDO.getOwnPropertyDescriptor("handleEvent");
  1018. listenerDO = listenerDO.proto;
  1019. }
  1020. if (heDesc && heDesc.value) {
  1021. listenerDO = heDesc.value;
  1022. }
  1023. }
  1024. // When the listener is a bound function, we are actually interested in
  1025. // the target function.
  1026. while (listenerDO.isBoundFunction) {
  1027. listenerDO = listenerDO.boundTargetFunction;
  1028. }
  1029. l.script = listenerDO.script;
  1030. // Chrome listeners won't be converted to debuggee values, since their
  1031. // compartment is not added as a debuggee.
  1032. if (!l.script)
  1033. continue;
  1034. listeners.push(l);
  1035. }
  1036. }
  1037. return listeners;
  1038. },
  1039. /**
  1040. * Set a breakpoint on the first line of the given script that has an entry
  1041. * point.
  1042. */
  1043. _breakOnEnter: function (script) {
  1044. let offsets = script.getAllOffsets();
  1045. for (let line = 0, n = offsets.length; line < n; line++) {
  1046. if (offsets[line]) {
  1047. // N.B. Hidden breakpoints do not have an original location, and are not
  1048. // stored in the breakpoint actor map.
  1049. let actor = new BreakpointActor(this);
  1050. this.threadLifetimePool.addActor(actor);
  1051. let scripts = this.dbg.findScripts({ source: script.source, line: line });
  1052. let entryPoints = findEntryPointsForLine(scripts, line);
  1053. setBreakpointAtEntryPoints(actor, entryPoints);
  1054. this._hiddenBreakpoints.set(actor.actorID, actor);
  1055. break;
  1056. }
  1057. }
  1058. },
  1059. /**
  1060. * Helper method that returns the next frame when stepping.
  1061. */
  1062. _getNextStepFrame: function (aFrame) {
  1063. let stepFrame = aFrame.reportedPop ? aFrame.older : aFrame;
  1064. if (!stepFrame || !stepFrame.script) {
  1065. stepFrame = null;
  1066. }
  1067. return stepFrame;
  1068. },
  1069. onClientEvaluate: function (aRequest) {
  1070. if (this.state !== "paused") {
  1071. return { error: "wrongState",
  1072. message: "Debuggee must be paused to evaluate code." };
  1073. }
  1074. let frame = this._requestFrame(aRequest.frame);
  1075. if (!frame) {
  1076. return { error: "unknownFrame",
  1077. message: "Evaluation frame not found" };
  1078. }
  1079. if (!frame.environment) {
  1080. return { error: "notDebuggee",
  1081. message: "cannot access the environment of this frame." };
  1082. }
  1083. let youngest = this.youngestFrame;
  1084. // Put ourselves back in the running state and inform the client.
  1085. let resumedPacket = this._resumed();
  1086. this.conn.send(resumedPacket);
  1087. // Run the expression.
  1088. // XXX: test syntax errors
  1089. let completion = frame.eval(aRequest.expression);
  1090. // Put ourselves back in the pause state.
  1091. let packet = this._paused(youngest);
  1092. packet.why = { type: "clientEvaluated",
  1093. frameFinished: this.createProtocolCompletionValue(completion) };
  1094. // Return back to our previous pause's event loop.
  1095. return packet;
  1096. },
  1097. onFrames: function (aRequest) {
  1098. if (this.state !== "paused") {
  1099. return { error: "wrongState",
  1100. message: "Stack frames are only available while the debuggee is paused."};
  1101. }
  1102. let start = aRequest.start ? aRequest.start : 0;
  1103. let count = aRequest.count;
  1104. // Find the starting frame...
  1105. let frame = this.youngestFrame;
  1106. let i = 0;
  1107. while (frame && (i < start)) {
  1108. frame = frame.older;
  1109. i++;
  1110. }
  1111. // Return request.count frames, or all remaining
  1112. // frames if count is not defined.
  1113. let promises = [];
  1114. for (; frame && (!count || i < (start + count)); i++, frame = frame.older) {
  1115. let form = this._createFrameActor(frame).form();
  1116. form.depth = i;
  1117. let promise = this.sources.getOriginalLocation(new GeneratedLocation(
  1118. this.sources.createNonSourceMappedActor(frame.script.source),
  1119. form.where.line,
  1120. form.where.column
  1121. )).then((originalLocation) => {
  1122. if (!originalLocation.originalSourceActor) {
  1123. return null;
  1124. }
  1125. let sourceForm = originalLocation.originalSourceActor.form();
  1126. form.where = {
  1127. source: sourceForm,
  1128. line: originalLocation.originalLine,
  1129. column: originalLocation.originalColumn
  1130. };
  1131. form.source = sourceForm;
  1132. return form;
  1133. });
  1134. promises.push(promise);
  1135. }
  1136. return all(promises).then(function (frames) {
  1137. // Filter null values because sourcemapping may have failed.
  1138. return { frames: frames.filter(x => !!x) };
  1139. });
  1140. },
  1141. onReleaseMany: function (aRequest) {
  1142. if (!aRequest.actors) {
  1143. return { error: "missingParameter",
  1144. message: "no actors were specified" };
  1145. }
  1146. let res;
  1147. for (let actorID of aRequest.actors) {
  1148. let actor = this.threadLifetimePool.get(actorID);
  1149. if (!actor) {
  1150. if (!res) {
  1151. res = { error: "notReleasable",
  1152. message: "Only thread-lifetime actors can be released." };
  1153. }
  1154. continue;
  1155. }
  1156. actor.onRelease();
  1157. }
  1158. return res ? res : {};
  1159. },
  1160. /**
  1161. * Get the script and source lists from the debugger.
  1162. */
  1163. _discoverSources: function () {
  1164. // Only get one script per Debugger.Source.
  1165. const sourcesToScripts = new Map();
  1166. const scripts = this.dbg.findScripts();
  1167. for (let i = 0, len = scripts.length; i < len; i++) {
  1168. let s = scripts[i];
  1169. if (s.source) {
  1170. sourcesToScripts.set(s.source, s);
  1171. }
  1172. }
  1173. return all([...sourcesToScripts.values()].map(script => {
  1174. return this.sources.createSourceActors(script.source);
  1175. }));
  1176. },
  1177. onSources: function (aRequest) {
  1178. return this._discoverSources().then(() => {
  1179. // No need to flush the new source packets here, as we are sending the
  1180. // list of sources out immediately and we don't need to invoke the
  1181. // overhead of an RDP packet for every source right now. Let the default
  1182. // timeout flush the buffered packets.
  1183. return {
  1184. sources: this.sources.iter().map(s => s.form())
  1185. };
  1186. });
  1187. },
  1188. /**
  1189. * Disassociate all breakpoint actors from their scripts and clear the
  1190. * breakpoint handlers. This method can be used when the thread actor intends
  1191. * to keep the breakpoint store, but needs to clear any actual breakpoints,
  1192. * e.g. due to a page navigation. This way the breakpoint actors' script
  1193. * caches won't hold on to the Debugger.Script objects leaking memory.
  1194. */
  1195. disableAllBreakpoints: function () {
  1196. for (let bpActor of this.breakpointActorMap.findActors()) {
  1197. bpActor.removeScripts();
  1198. }
  1199. },
  1200. /**
  1201. * Handle a protocol request to pause the debuggee.
  1202. */
  1203. onInterrupt: function (aRequest) {
  1204. if (this.state == "exited") {
  1205. return { type: "exited" };
  1206. } else if (this.state == "paused") {
  1207. // TODO: return the actual reason for the existing pause.
  1208. return { type: "paused", why: { type: "alreadyPaused" } };
  1209. } else if (this.state != "running") {
  1210. return { error: "wrongState",
  1211. message: "Received interrupt request in " + this.state +
  1212. " state." };
  1213. }
  1214. try {
  1215. // If execution should pause just before the next JavaScript bytecode is
  1216. // executed, just set an onEnterFrame handler.
  1217. if (aRequest.when == "onNext") {
  1218. let onEnterFrame = (aFrame) => {
  1219. return this._pauseAndRespond(aFrame, { type: "interrupted", onNext: true });
  1220. };
  1221. this.dbg.onEnterFrame = onEnterFrame;
  1222. return { type: "willInterrupt" };
  1223. }
  1224. // If execution should pause immediately, just put ourselves in the paused
  1225. // state.
  1226. let packet = this._paused();
  1227. if (!packet) {
  1228. return { error: "notInterrupted" };
  1229. }
  1230. packet.why = { type: "interrupted" };
  1231. // Send the response to the interrupt request now (rather than
  1232. // returning it), because we're going to start a nested event loop
  1233. // here.
  1234. this.conn.send(packet);
  1235. // Start a nested event loop.
  1236. this._pushThreadPause();
  1237. // We already sent a response to this request, don't send one
  1238. // now.
  1239. return null;
  1240. } catch (e) {
  1241. reportError(e);
  1242. return { error: "notInterrupted", message: e.toString() };
  1243. }
  1244. },
  1245. /**
  1246. * Handle a protocol request to retrieve all the event listeners on the page.
  1247. */
  1248. onEventListeners: function (aRequest) {
  1249. // This request is only supported in content debugging.
  1250. if (!this.global) {
  1251. return {
  1252. error: "notImplemented",
  1253. message: "eventListeners request is only supported in content debugging"
  1254. };
  1255. }
  1256. let els = Cc["@mozilla.org/eventlistenerservice;1"]
  1257. .getService(Ci.nsIEventListenerService);
  1258. let nodes = this.global.document.getElementsByTagName("*");
  1259. nodes = [this.global].concat([].slice.call(nodes));
  1260. let listeners = [];
  1261. for (let node of nodes) {
  1262. let handlers = els.getListenerInfoFor(node);
  1263. for (let handler of handlers) {
  1264. // Create a form object for serializing the listener via the protocol.
  1265. let listenerForm = Object.create(null);
  1266. let listener = handler.listenerObject;
  1267. // Native event listeners don't provide any listenerObject or type and
  1268. // are not that useful to a JS debugger.
  1269. if (!listener || !handler.type) {
  1270. continue;
  1271. }
  1272. // There will be no tagName if the event listener is set on the window.
  1273. let selector = node.tagName ? CssLogic.findCssSelector(node) : "window";
  1274. let nodeDO = this.globalDebugObject.makeDebuggeeValue(node);
  1275. listenerForm.node = {
  1276. selector: selector,
  1277. object: createValueGrip(nodeDO, this._pausePool, this.objectGrip)
  1278. };
  1279. listenerForm.type = handler.type;
  1280. listenerForm.capturing = handler.capturing;
  1281. listenerForm.allowsUntrusted = handler.allowsUntrusted;
  1282. listenerForm.inSystemEventGroup = handler.inSystemEventGroup;
  1283. let handlerName = "on" + listenerForm.type;
  1284. listenerForm.isEventHandler = false;
  1285. if (typeof node.hasAttribute !== "undefined") {
  1286. listenerForm.isEventHandler = !!node.hasAttribute(handlerName);
  1287. }
  1288. if (!!node[handlerName]) {
  1289. listenerForm.isEventHandler = !!node[handlerName];
  1290. }
  1291. // Get the Debugger.Object for the listener object.
  1292. let listenerDO = this.globalDebugObject.makeDebuggeeValue(listener);
  1293. // If the listener is an object with a 'handleEvent' method, use that.
  1294. if (listenerDO.class == "Object" || listenerDO.class == "XULElement") {
  1295. // For some events we don't have permission to access the
  1296. // 'handleEvent' property when running in content scope.
  1297. if (!listenerDO.unwrap()) {
  1298. continue;
  1299. }
  1300. let heDesc;
  1301. while (!heDesc && listenerDO) {
  1302. heDesc = listenerDO.getOwnPropertyDescriptor("handleEvent");
  1303. listenerDO = listenerDO.proto;
  1304. }
  1305. if (heDesc && heDesc.value) {
  1306. listenerDO = heDesc.value;
  1307. }
  1308. }
  1309. // When the listener is a bound function, we are actually interested in
  1310. // the target function.
  1311. while (listenerDO.isBoundFunction) {
  1312. listenerDO = listenerDO.boundTargetFunction;
  1313. }
  1314. listenerForm.function = createValueGrip(listenerDO, this._pausePool,
  1315. this.objectGrip);
  1316. listeners.push(listenerForm);
  1317. }
  1318. }
  1319. return { listeners: listeners };
  1320. },
  1321. /**
  1322. * Return the Debug.Frame for a frame mentioned by the protocol.
  1323. */
  1324. _requestFrame: function (aFrameID) {
  1325. if (!aFrameID) {
  1326. return this.youngestFrame;
  1327. }
  1328. if (this._framePool.has(aFrameID)) {
  1329. return this._framePool.get(aFrameID).frame;
  1330. }
  1331. return undefined;
  1332. },
  1333. _paused: function (aFrame) {
  1334. // We don't handle nested pauses correctly. Don't try - if we're
  1335. // paused, just continue running whatever code triggered the pause.
  1336. // We don't want to actually have nested pauses (although we
  1337. // have nested event loops). If code runs in the debuggee during
  1338. // a pause, it should cause the actor to resume (dropping
  1339. // pause-lifetime actors etc) and then repause when complete.
  1340. if (this.state === "paused") {
  1341. return undefined;
  1342. }
  1343. // Clear stepping hooks.
  1344. this.dbg.onEnterFrame = undefined;
  1345. this.dbg.onExceptionUnwind = undefined;
  1346. if (aFrame) {
  1347. aFrame.onStep = undefined;
  1348. aFrame.onPop = undefined;
  1349. }
  1350. // Clear DOM event breakpoints.
  1351. // XPCShell tests don't use actual DOM windows for globals and cause
  1352. // removeListenerForAllEvents to throw.
  1353. if (!isWorker && this.global && !this.global.toString().includes("Sandbox")) {
  1354. let els = Cc["@mozilla.org/eventlistenerservice;1"]
  1355. .getService(Ci.nsIEventListenerService);
  1356. els.removeListenerForAllEvents(this.global, this._allEventsListener, true);
  1357. for (let [, bp] of this._hiddenBreakpoints) {
  1358. bp.delete();
  1359. }
  1360. this._hiddenBreakpoints.clear();
  1361. }
  1362. this._state = "paused";
  1363. // Create the actor pool that will hold the pause actor and its
  1364. // children.
  1365. assert(!this._pausePool, "No pause pool should exist yet");
  1366. this._pausePool = new ActorPool(this.conn);
  1367. this.conn.addActorPool(this._pausePool);
  1368. // Give children of the pause pool a quick link back to the
  1369. // thread...
  1370. this._pausePool.threadActor = this;
  1371. // Create the pause actor itself...
  1372. assert(!this._pauseActor, "No pause actor should exist yet");
  1373. this._pauseActor = new PauseActor(this._pausePool);
  1374. this._pausePool.addActor(this._pauseActor);
  1375. // Update the list of frames.
  1376. let poppedFrames = this._updateFrames();
  1377. // Send off the paused packet and spin an event loop.
  1378. let packet = { from: this.actorID,
  1379. type: "paused",
  1380. actor: this._pauseActor.actorID };
  1381. if (aFrame) {
  1382. packet.frame = this._createFrameActor(aFrame).form();
  1383. }
  1384. if (poppedFrames) {
  1385. packet.poppedFrames = poppedFrames;
  1386. }
  1387. return packet;
  1388. },
  1389. _resumed: function () {
  1390. this._state = "running";
  1391. // Drop the actors in the pause actor pool.
  1392. this.conn.removeActorPool(this._pausePool);
  1393. this._pausePool = null;
  1394. this._pauseActor = null;
  1395. return { from: this.actorID, type: "resumed" };
  1396. },
  1397. /**
  1398. * Expire frame actors for frames that have been popped.
  1399. *
  1400. * @returns A list of actor IDs whose frames have been popped.
  1401. */
  1402. _updateFrames: function () {
  1403. let popped = [];
  1404. // Create the actor pool that will hold the still-living frames.
  1405. let framePool = new ActorPool(this.conn);
  1406. let frameList = [];
  1407. for (let frameActor of this._frameActors) {
  1408. if (frameActor.frame.live) {
  1409. framePool.addActor(frameActor);
  1410. frameList.push(frameActor);
  1411. } else {
  1412. popped.push(frameActor.actorID);
  1413. }
  1414. }
  1415. // Remove the old frame actor pool, this will expire
  1416. // any actors that weren't added to the new pool.
  1417. if (this._framePool) {
  1418. this.conn.removeActorPool(this._framePool);
  1419. }
  1420. this._frameActors = frameList;
  1421. this._framePool = framePool;
  1422. this.conn.addActorPool(framePool);
  1423. return popped;
  1424. },
  1425. _createFrameActor: function (aFrame) {
  1426. if (aFrame.actor) {
  1427. return aFrame.actor;
  1428. }
  1429. let actor = new FrameActor(aFrame, this);
  1430. this._frameActors.push(actor);
  1431. this._framePool.addActor(actor);
  1432. aFrame.actor = actor;
  1433. return actor;
  1434. },
  1435. /**
  1436. * Create and return an environment actor that corresponds to the provided
  1437. * Debugger.Environment.
  1438. * @param Debugger.Environment aEnvironment
  1439. * The lexical environment we want to extract.
  1440. * @param object aPool
  1441. * The pool where the newly-created actor will be placed.
  1442. * @return The EnvironmentActor for aEnvironment or undefined for host
  1443. * functions or functions scoped to a non-debuggee global.
  1444. */
  1445. createEnvironmentActor: function (aEnvironment, aPool) {
  1446. if (!aEnvironment) {
  1447. return undefined;
  1448. }
  1449. if (aEnvironment.actor) {
  1450. return aEnvironment.actor;
  1451. }
  1452. let actor = new EnvironmentActor(aEnvironment, this);
  1453. aPool.addActor(actor);
  1454. aEnvironment.actor = actor;
  1455. return actor;
  1456. },
  1457. /**
  1458. * Return a protocol completion value representing the given
  1459. * Debugger-provided completion value.
  1460. */
  1461. createProtocolCompletionValue: function (aCompletion) {
  1462. let protoValue = {};
  1463. if (aCompletion == null) {
  1464. protoValue.terminated = true;
  1465. } else if ("return" in aCompletion) {
  1466. protoValue.return = createValueGrip(aCompletion.return,
  1467. this._pausePool, this.objectGrip);
  1468. } else if ("throw" in aCompletion) {
  1469. protoValue.throw = createValueGrip(aCompletion.throw,
  1470. this._pausePool, this.objectGrip);
  1471. } else {
  1472. protoValue.return = createValueGrip(aCompletion.yield,
  1473. this._pausePool, this.objectGrip);
  1474. }
  1475. return protoValue;
  1476. },
  1477. /**
  1478. * Create a grip for the given debuggee object.
  1479. *
  1480. * @param aValue Debugger.Object
  1481. * The debuggee object value.
  1482. * @param aPool ActorPool
  1483. * The actor pool where the new object actor will be added.
  1484. */
  1485. objectGrip: function (aValue, aPool) {
  1486. if (!aPool.objectActors) {
  1487. aPool.objectActors = new WeakMap();
  1488. }
  1489. if (aPool.objectActors.has(aValue)) {
  1490. return aPool.objectActors.get(aValue).grip();
  1491. } else if (this.threadLifetimePool.objectActors.has(aValue)) {
  1492. return this.threadLifetimePool.objectActors.get(aValue).grip();
  1493. }
  1494. let actor = new PauseScopedObjectActor(aValue, {
  1495. getGripDepth: () => this._gripDepth,
  1496. incrementGripDepth: () => this._gripDepth++,
  1497. decrementGripDepth: () => this._gripDepth--,
  1498. createValueGrip: v => createValueGrip(v, this._pausePool,
  1499. this.pauseObjectGrip),
  1500. sources: () => this.sources,
  1501. createEnvironmentActor: (env, pool) =>
  1502. this.createEnvironmentActor(env, pool),
  1503. promote: () => this.threadObjectGrip(actor),
  1504. isThreadLifetimePool: () =>
  1505. actor.registeredPool !== this.threadLifetimePool,
  1506. getGlobalDebugObject: () => this.globalDebugObject
  1507. });
  1508. aPool.addActor(actor);
  1509. aPool.objectActors.set(aValue, actor);
  1510. return actor.grip();
  1511. },
  1512. /**
  1513. * Create a grip for the given debuggee object with a pause lifetime.
  1514. *
  1515. * @param aValue Debugger.Object
  1516. * The debuggee object value.
  1517. */
  1518. pauseObjectGrip: function (aValue) {
  1519. if (!this._pausePool) {
  1520. throw "Object grip requested while not paused.";
  1521. }
  1522. return this.objectGrip(aValue, this._pausePool);
  1523. },
  1524. /**
  1525. * Extend the lifetime of the provided object actor to thread lifetime.
  1526. *
  1527. * @param aActor object
  1528. * The object actor.
  1529. */
  1530. threadObjectGrip: function (aActor) {
  1531. // We want to reuse the existing actor ID, so we just remove it from the
  1532. // current pool's weak map and then let pool.addActor do the rest.
  1533. aActor.registeredPool.objectActors.delete(aActor.obj);
  1534. this.threadLifetimePool.addActor(aActor);
  1535. this.threadLifetimePool.objectActors.set(aActor.obj, aActor);
  1536. },
  1537. /**
  1538. * Handle a protocol request to promote multiple pause-lifetime grips to
  1539. * thread-lifetime grips.
  1540. *
  1541. * @param aRequest object
  1542. * The protocol request object.
  1543. */
  1544. onThreadGrips: function (aRequest) {
  1545. if (this.state != "paused") {
  1546. return { error: "wrongState" };
  1547. }
  1548. if (!aRequest.actors) {
  1549. return { error: "missingParameter",
  1550. message: "no actors were specified" };
  1551. }
  1552. for (let actorID of aRequest.actors) {
  1553. let actor = this._pausePool.get(actorID);
  1554. if (actor) {
  1555. this.threadObjectGrip(actor);
  1556. }
  1557. }
  1558. return {};
  1559. },
  1560. /**
  1561. * Create a long string grip that is scoped to a pause.
  1562. *
  1563. * @param aString String
  1564. * The string we are creating a grip for.
  1565. */
  1566. pauseLongStringGrip: function (aString) {
  1567. return longStringGrip(aString, this._pausePool);
  1568. },
  1569. /**
  1570. * Create a long string grip that is scoped to a thread.
  1571. *
  1572. * @param aString String
  1573. * The string we are creating a grip for.
  1574. */
  1575. threadLongStringGrip: function (aString) {
  1576. return longStringGrip(aString, this._threadLifetimePool);
  1577. },
  1578. // JS Debugger API hooks.
  1579. /**
  1580. * A function that the engine calls when a call to a debug event hook,
  1581. * breakpoint handler, watchpoint handler, or similar function throws some
  1582. * exception.
  1583. *
  1584. * @param aException exception
  1585. * The exception that was thrown in the debugger code.
  1586. */
  1587. uncaughtExceptionHook: function (aException) {
  1588. dumpn("Got an exception: " + aException.message + "\n" + aException.stack);
  1589. },
  1590. /**
  1591. * A function that the engine calls when a debugger statement has been
  1592. * executed in the specified frame.
  1593. *
  1594. * @param aFrame Debugger.Frame
  1595. * The stack frame that contained the debugger statement.
  1596. */
  1597. onDebuggerStatement: function (aFrame) {
  1598. // Don't pause if we are currently stepping (in or over) or the frame is
  1599. // black-boxed.
  1600. const generatedLocation = this.sources.getFrameLocation(aFrame);
  1601. const { originalSourceActor } = this.unsafeSynchronize(this.sources.getOriginalLocation(
  1602. generatedLocation));
  1603. const url = originalSourceActor ? originalSourceActor.url : null;
  1604. return this.sources.isBlackBoxed(url) || aFrame.onStep
  1605. ? undefined
  1606. : this._pauseAndRespond(aFrame, { type: "debuggerStatement" });
  1607. },
  1608. /**
  1609. * A function that the engine calls when an exception has been thrown and has
  1610. * propagated to the specified frame.
  1611. *
  1612. * @param aFrame Debugger.Frame
  1613. * The youngest remaining stack frame.
  1614. * @param aValue object
  1615. * The exception that was thrown.
  1616. */
  1617. onExceptionUnwind: function (aFrame, aValue) {
  1618. let willBeCaught = false;
  1619. for (let frame = aFrame; frame != null; frame = frame.older) {
  1620. if (frame.script.isInCatchScope(frame.offset)) {
  1621. willBeCaught = true;
  1622. break;
  1623. }
  1624. }
  1625. if (willBeCaught && this._options.ignoreCaughtExceptions) {
  1626. return undefined;
  1627. }
  1628. // NS_ERROR_NO_INTERFACE exceptions are a special case in browser code,
  1629. // since they're almost always thrown by QueryInterface functions, and
  1630. // handled cleanly by native code.
  1631. if (aValue == Cr.NS_ERROR_NO_INTERFACE) {
  1632. return undefined;
  1633. }
  1634. const generatedLocation = this.sources.getFrameLocation(aFrame);
  1635. const { originalSourceActor } = this.unsafeSynchronize(this.sources.getOriginalLocation(
  1636. generatedLocation));
  1637. const url = originalSourceActor ? originalSourceActor.url : null;
  1638. if (this.sources.isBlackBoxed(url)) {
  1639. return undefined;
  1640. }
  1641. try {
  1642. let packet = this._paused(aFrame);
  1643. if (!packet) {
  1644. return undefined;
  1645. }
  1646. packet.why = { type: "exception",
  1647. exception: createValueGrip(aValue, this._pausePool,
  1648. this.objectGrip)
  1649. };
  1650. this.conn.send(packet);
  1651. this._pushThreadPause();
  1652. } catch (e) {
  1653. reportError(e, "Got an exception during TA_onExceptionUnwind: ");
  1654. }
  1655. return undefined;
  1656. },
  1657. /**
  1658. * A function that the engine calls when a new script has been loaded into the
  1659. * scope of the specified debuggee global.
  1660. *
  1661. * @param aScript Debugger.Script
  1662. * The source script that has been loaded into a debuggee compartment.
  1663. * @param aGlobal Debugger.Object
  1664. * A Debugger.Object instance whose referent is the global object.
  1665. */
  1666. onNewScript: function (aScript, aGlobal) {
  1667. this._addSource(aScript.source);
  1668. },
  1669. /**
  1670. * A function called when there's a new or updated source from a thread actor's
  1671. * sources. Emits `newSource` and `updatedSource` on the tab actor.
  1672. *
  1673. * @param {String} name
  1674. * @param {SourceActor} source
  1675. */
  1676. onSourceEvent: function (name, source) {
  1677. this.conn.send({
  1678. from: this._parent.actorID,
  1679. type: name,
  1680. source: source.form()
  1681. });
  1682. // For compatibility and debugger still using `newSource` on the thread client,
  1683. // still emit this event here. Clean up in bug 1247084
  1684. if (name === "newSource") {
  1685. this.conn.send({
  1686. from: this.actorID,
  1687. type: name,
  1688. source: source.form()
  1689. });
  1690. }
  1691. },
  1692. /**
  1693. * Add the provided source to the server cache.
  1694. *
  1695. * @param aSource Debugger.Source
  1696. * The source that will be stored.
  1697. * @returns true, if the source was added; false otherwise.
  1698. */
  1699. _addSource: function (aSource) {
  1700. if (!this.sources.allowSource(aSource) || this._debuggerSourcesSeen.has(aSource)) {
  1701. return false;
  1702. }
  1703. let sourceActor = this.sources.createNonSourceMappedActor(aSource);
  1704. let bpActors = [...this.breakpointActorMap.findActors()];
  1705. if (this._options.useSourceMaps) {
  1706. let promises = [];
  1707. // Go ahead and establish the source actors for this script, which
  1708. // fetches sourcemaps if available and sends onNewSource
  1709. // notifications.
  1710. let sourceActorsCreated = this.sources._createSourceMappedActors(aSource);
  1711. if (bpActors.length) {
  1712. // We need to use unsafeSynchronize here because if the page is being reloaded,
  1713. // this call will replace the previous set of source actors for this source
  1714. // with a new one. If the source actors have not been replaced by the time
  1715. // we try to reset the breakpoints below, their location objects will still
  1716. // point to the old set of source actors, which point to different
  1717. // scripts.
  1718. this.unsafeSynchronize(sourceActorsCreated);
  1719. }
  1720. for (let _actor of bpActors) {
  1721. // XXX bug 1142115: We do async work in here, so we need to create a fresh
  1722. // binding because for/of does not yet do that in SpiderMonkey.
  1723. let actor = _actor;
  1724. if (actor.isPending) {
  1725. promises.push(actor.originalLocation.originalSourceActor._setBreakpoint(actor));
  1726. } else {
  1727. promises.push(this.sources.getAllGeneratedLocations(actor.originalLocation)
  1728. .then((generatedLocations) => {
  1729. if (generatedLocations.length > 0 &&
  1730. generatedLocations[0].generatedSourceActor.actorID === sourceActor.actorID) {
  1731. sourceActor._setBreakpointAtAllGeneratedLocations(actor, generatedLocations);
  1732. }
  1733. }));
  1734. }
  1735. }
  1736. if (promises.length > 0) {
  1737. this.unsafeSynchronize(promise.all(promises));
  1738. }
  1739. } else {
  1740. // Bug 1225160: If addSource is called in response to a new script
  1741. // notification, and this notification was triggered by loading a JSM from
  1742. // chrome code, calling unsafeSynchronize could cause a debuggee timer to
  1743. // fire. If this causes the JSM to be loaded a second time, the browser
  1744. // will crash, because loading JSMS is not reentrant, and the first load
  1745. // has not completed yet.
  1746. //
  1747. // The root of the problem is that unsafeSynchronize can cause debuggee
  1748. // code to run. Unfortunately, fixing that is prohibitively difficult. The
  1749. // best we can do at the moment is disable source maps for the browser
  1750. // debugger, and carefully avoid the use of unsafeSynchronize in this
  1751. // function when source maps are disabled.
  1752. for (let actor of bpActors) {
  1753. if (actor.isPending) {
  1754. actor.originalLocation.originalSourceActor._setBreakpoint(actor);
  1755. } else {
  1756. actor.originalLocation.originalSourceActor._setBreakpointAtGeneratedLocation(
  1757. actor, GeneratedLocation.fromOriginalLocation(actor.originalLocation)
  1758. );
  1759. }
  1760. }
  1761. }
  1762. this._debuggerSourcesSeen.add(aSource);
  1763. return true;
  1764. },
  1765. /**
  1766. * Get prototypes and properties of multiple objects.
  1767. */
  1768. onPrototypesAndProperties: function (aRequest) {
  1769. let result = {};
  1770. for (let actorID of aRequest.actors) {
  1771. // This code assumes that there are no lazily loaded actors returned
  1772. // by this call.
  1773. let actor = this.conn.getActor(actorID);
  1774. if (!actor) {
  1775. return { from: this.actorID,
  1776. error: "noSuchActor" };
  1777. }
  1778. let handler = actor.onPrototypeAndProperties;
  1779. if (!handler) {
  1780. return { from: this.actorID,
  1781. error: "unrecognizedPacketType",
  1782. message: ('Actor "' + actorID +
  1783. '" does not recognize the packet type ' +
  1784. '"prototypeAndProperties"') };
  1785. }
  1786. result[actorID] = handler.call(actor, {});
  1787. }
  1788. return { from: this.actorID,
  1789. actors: result };
  1790. }
  1791. });
  1792. ThreadActor.prototype.requestTypes = object.merge(ThreadActor.prototype.requestTypes, {
  1793. "attach": ThreadActor.prototype.onAttach,
  1794. "detach": ThreadActor.prototype.onDetach,
  1795. "reconfigure": ThreadActor.prototype.onReconfigure,
  1796. "resume": ThreadActor.prototype.onResume,
  1797. "clientEvaluate": ThreadActor.prototype.onClientEvaluate,
  1798. "frames": ThreadActor.prototype.onFrames,
  1799. "interrupt": ThreadActor.prototype.onInterrupt,
  1800. "eventListeners": ThreadActor.prototype.onEventListeners,
  1801. "releaseMany": ThreadActor.prototype.onReleaseMany,
  1802. "sources": ThreadActor.prototype.onSources,
  1803. "threadGrips": ThreadActor.prototype.onThreadGrips,
  1804. "prototypesAndProperties": ThreadActor.prototype.onPrototypesAndProperties
  1805. });
  1806. exports.ThreadActor = ThreadActor;
  1807. /**
  1808. * Creates a PauseActor.
  1809. *
  1810. * PauseActors exist for the lifetime of a given debuggee pause. Used to
  1811. * scope pause-lifetime grips.
  1812. *
  1813. * @param ActorPool aPool
  1814. * The actor pool created for this pause.
  1815. */
  1816. function PauseActor(aPool)
  1817. {
  1818. this.pool = aPool;
  1819. }
  1820. PauseActor.prototype = {
  1821. actorPrefix: "pause"
  1822. };
  1823. /**
  1824. * A base actor for any actors that should only respond receive messages in the
  1825. * paused state. Subclasses may expose a `threadActor` which is used to help
  1826. * determine when we are in a paused state. Subclasses should set their own
  1827. * "constructor" property if they want better error messages. You should never
  1828. * instantiate a PauseScopedActor directly, only through subclasses.
  1829. */
  1830. function PauseScopedActor()
  1831. {
  1832. }
  1833. /**
  1834. * A function decorator for creating methods to handle protocol messages that
  1835. * should only be received while in the paused state.
  1836. *
  1837. * @param aMethod Function
  1838. * The function we are decorating.
  1839. */
  1840. PauseScopedActor.withPaused = function (aMethod) {
  1841. return function () {
  1842. if (this.isPaused()) {
  1843. return aMethod.apply(this, arguments);
  1844. } else {
  1845. return this._wrongState();
  1846. }
  1847. };
  1848. };
  1849. PauseScopedActor.prototype = {
  1850. /**
  1851. * Returns true if we are in the paused state.
  1852. */
  1853. isPaused: function () {
  1854. // When there is not a ThreadActor available (like in the webconsole) we
  1855. // have to be optimistic and assume that we are paused so that we can
  1856. // respond to requests.
  1857. return this.threadActor ? this.threadActor.state === "paused" : true;
  1858. },
  1859. /**
  1860. * Returns the wrongState response packet for this actor.
  1861. */
  1862. _wrongState: function () {
  1863. return {
  1864. error: "wrongState",
  1865. message: this.constructor.name +
  1866. " actors can only be accessed while the thread is paused."
  1867. };
  1868. }
  1869. };
  1870. /**
  1871. * Creates a pause-scoped actor for the specified object.
  1872. * @see ObjectActor
  1873. */
  1874. function PauseScopedObjectActor(obj, hooks) {
  1875. ObjectActor.call(this, obj, hooks);
  1876. this.hooks.promote = hooks.promote;
  1877. this.hooks.isThreadLifetimePool = hooks.isThreadLifetimePool;
  1878. }
  1879. PauseScopedObjectActor.prototype = Object.create(PauseScopedActor.prototype);
  1880. Object.assign(PauseScopedObjectActor.prototype, ObjectActor.prototype);
  1881. Object.assign(PauseScopedObjectActor.prototype, {
  1882. constructor: PauseScopedObjectActor,
  1883. actorPrefix: "pausedobj",
  1884. onOwnPropertyNames:
  1885. PauseScopedActor.withPaused(ObjectActor.prototype.onOwnPropertyNames),
  1886. onPrototypeAndProperties:
  1887. PauseScopedActor.withPaused(ObjectActor.prototype.onPrototypeAndProperties),
  1888. onPrototype: PauseScopedActor.withPaused(ObjectActor.prototype.onPrototype),
  1889. onProperty: PauseScopedActor.withPaused(ObjectActor.prototype.onProperty),
  1890. onDecompile: PauseScopedActor.withPaused(ObjectActor.prototype.onDecompile),
  1891. onDisplayString:
  1892. PauseScopedActor.withPaused(ObjectActor.prototype.onDisplayString),
  1893. onParameterNames:
  1894. PauseScopedActor.withPaused(ObjectActor.prototype.onParameterNames),
  1895. /**
  1896. * Handle a protocol request to promote a pause-lifetime grip to a
  1897. * thread-lifetime grip.
  1898. *
  1899. * @param aRequest object
  1900. * The protocol request object.
  1901. */
  1902. onThreadGrip: PauseScopedActor.withPaused(function (aRequest) {
  1903. this.hooks.promote();
  1904. return {};
  1905. }),
  1906. /**
  1907. * Handle a protocol request to release a thread-lifetime grip.
  1908. *
  1909. * @param aRequest object
  1910. * The protocol request object.
  1911. */
  1912. onRelease: PauseScopedActor.withPaused(function (aRequest) {
  1913. if (this.hooks.isThreadLifetimePool()) {
  1914. return { error: "notReleasable",
  1915. message: "Only thread-lifetime actors can be released." };
  1916. }
  1917. this.release();
  1918. return {};
  1919. }),
  1920. });
  1921. Object.assign(PauseScopedObjectActor.prototype.requestTypes, {
  1922. "threadGrip": PauseScopedObjectActor.prototype.onThreadGrip,
  1923. });
  1924. function hackDebugger(Debugger) {
  1925. // TODO: Improve native code instead of hacking on top of it
  1926. /**
  1927. * Override the toString method in order to get more meaningful script output
  1928. * for debugging the debugger.
  1929. */
  1930. Debugger.Script.prototype.toString = function () {
  1931. let output = "";
  1932. if (this.url) {
  1933. output += this.url;
  1934. }
  1935. if (typeof this.staticLevel != "undefined") {
  1936. output += ":L" + this.staticLevel;
  1937. }
  1938. if (typeof this.startLine != "undefined") {
  1939. output += ":" + this.startLine;
  1940. if (this.lineCount && this.lineCount > 1) {
  1941. output += "-" + (this.startLine + this.lineCount - 1);
  1942. }
  1943. }
  1944. if (typeof this.startLine != "undefined") {
  1945. output += ":" + this.startLine;
  1946. if (this.lineCount && this.lineCount > 1) {
  1947. output += "-" + (this.startLine + this.lineCount - 1);
  1948. }
  1949. }
  1950. if (this.strictMode) {
  1951. output += ":strict";
  1952. }
  1953. return output;
  1954. };
  1955. /**
  1956. * Helper property for quickly getting to the line number a stack frame is
  1957. * currently paused at.
  1958. */
  1959. Object.defineProperty(Debugger.Frame.prototype, "line", {
  1960. configurable: true,
  1961. get: function () {
  1962. if (this.script) {
  1963. return this.script.getOffsetLocation(this.offset).lineNumber;
  1964. } else {
  1965. return null;
  1966. }
  1967. }
  1968. });
  1969. }
  1970. /**
  1971. * Creates an actor for handling chrome debugging. ChromeDebuggerActor is a
  1972. * thin wrapper over ThreadActor, slightly changing some of its behavior.
  1973. *
  1974. * @param aConnection object
  1975. * The DebuggerServerConnection with which this ChromeDebuggerActor
  1976. * is associated. (Currently unused, but required to make this
  1977. * constructor usable with addGlobalActor.)
  1978. *
  1979. * @param aParent object
  1980. * This actor's parent actor. See ThreadActor for a list of expected
  1981. * properties.
  1982. */
  1983. function ChromeDebuggerActor(aConnection, aParent)
  1984. {
  1985. ThreadActor.prototype.initialize.call(this, aParent);
  1986. }
  1987. ChromeDebuggerActor.prototype = Object.create(ThreadActor.prototype);
  1988. Object.assign(ChromeDebuggerActor.prototype, {
  1989. constructor: ChromeDebuggerActor,
  1990. // A constant prefix that will be used to form the actor ID by the server.
  1991. actorPrefix: "chromeDebugger"
  1992. });
  1993. exports.ChromeDebuggerActor = ChromeDebuggerActor;
  1994. /**
  1995. * Creates an actor for handling add-on debugging. AddonThreadActor is
  1996. * a thin wrapper over ThreadActor.
  1997. *
  1998. * @param aConnection object
  1999. * The DebuggerServerConnection with which this AddonThreadActor
  2000. * is associated. (Currently unused, but required to make this
  2001. * constructor usable with addGlobalActor.)
  2002. *
  2003. * @param aParent object
  2004. * This actor's parent actor. See ThreadActor for a list of expected
  2005. * properties.
  2006. */
  2007. function AddonThreadActor(aConnect, aParent) {
  2008. ThreadActor.prototype.initialize.call(this, aParent);
  2009. }
  2010. AddonThreadActor.prototype = Object.create(ThreadActor.prototype);
  2011. Object.assign(AddonThreadActor.prototype, {
  2012. constructor: AddonThreadActor,
  2013. // A constant prefix that will be used to form the actor ID by the server.
  2014. actorPrefix: "addonThread"
  2015. });
  2016. exports.AddonThreadActor = AddonThreadActor;
  2017. // Utility functions.
  2018. /**
  2019. * Report the given error in the error console and to stdout.
  2020. *
  2021. * @param Error aError
  2022. * The error object you wish to report.
  2023. * @param String aPrefix
  2024. * An optional prefix for the reported error message.
  2025. */
  2026. var oldReportError = reportError;
  2027. reportError = function (aError, aPrefix = "") {
  2028. assert(aError instanceof Error, "Must pass Error objects to reportError");
  2029. let msg = aPrefix + aError.message + ":\n" + aError.stack;
  2030. oldReportError(msg);
  2031. dumpn(msg);
  2032. };
  2033. /**
  2034. * Find the scripts which contain offsets that are an entry point to the given
  2035. * line.
  2036. *
  2037. * @param Array scripts
  2038. * The set of Debugger.Scripts to consider.
  2039. * @param Number line
  2040. * The line we are searching for entry points into.
  2041. * @returns Array of objects of the form { script, offsets } where:
  2042. * - script is a Debugger.Script
  2043. * - offsets is an array of offsets that are entry points into the
  2044. * given line.
  2045. */
  2046. function findEntryPointsForLine(scripts, line) {
  2047. const entryPoints = [];
  2048. for (let script of scripts) {
  2049. const offsets = script.getLineOffsets(line);
  2050. if (offsets.length) {
  2051. entryPoints.push({ script, offsets });
  2052. }
  2053. }
  2054. return entryPoints;
  2055. }
  2056. /**
  2057. * Unwrap a global that is wrapped in a |Debugger.Object|, or if the global has
  2058. * become a dead object, return |undefined|.
  2059. *
  2060. * @param Debugger.Object wrappedGlobal
  2061. * The |Debugger.Object| which wraps a global.
  2062. *
  2063. * @returns {Object|undefined}
  2064. * Returns the unwrapped global object or |undefined| if unwrapping
  2065. * failed.
  2066. */
  2067. exports.unwrapDebuggerObjectGlobal = wrappedGlobal => {
  2068. try {
  2069. // Because of bug 991399 we sometimes get nuked window references here. We
  2070. // just bail out in that case.
  2071. //
  2072. // Note that addon sandboxes have a DOMWindow as their prototype. So make
  2073. // sure that we can touch the prototype too (whatever it is), in case _it_
  2074. // is it a nuked window reference. We force stringification to make sure
  2075. // that any dead object proxies make themselves known.
  2076. let global = wrappedGlobal.unsafeDereference();
  2077. Object.getPrototypeOf(global) + "";
  2078. return global;
  2079. }
  2080. catch (e) {
  2081. return undefined;
  2082. }
  2083. };