head_dbg.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863
  1. /* Any copyright is dedicated to the Public Domain.
  2. http://creativecommons.org/publicdomain/zero/1.0/ */
  3. "use strict";
  4. var Cc = Components.classes;
  5. var Ci = Components.interfaces;
  6. var Cu = Components.utils;
  7. var Cr = Components.results;
  8. var CC = Components.Constructor;
  9. // Populate AppInfo before anything (like the shared loader) accesses
  10. // System.appinfo, which is a lazy getter.
  11. const _appInfo = {};
  12. Cu.import("resource://testing-common/AppInfo.jsm", _appInfo);
  13. _appInfo.updateAppInfo({
  14. ID: "devtools@tests.mozilla.org",
  15. name: "devtools-tests",
  16. version: "1",
  17. platformVersion: "42",
  18. crashReporter: true,
  19. });
  20. const { require, loader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
  21. const { worker } = Cu.import("resource://devtools/shared/worker/loader.js", {});
  22. const promise = require("promise");
  23. const { Task } = require("devtools/shared/task");
  24. const { console } = require("resource://gre/modules/Console.jsm");
  25. const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
  26. const Services = require("Services");
  27. // Always log packets when running tests. runxpcshelltests.py will throw
  28. // the output away anyway, unless you give it the --verbose flag.
  29. Services.prefs.setBoolPref("devtools.debugger.log", true);
  30. // Enable remote debugging for the relevant tests.
  31. Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
  32. const DevToolsUtils = require("devtools/shared/DevToolsUtils");
  33. const { DebuggerServer } = require("devtools/server/main");
  34. const { DebuggerServer: WorkerDebuggerServer } = worker.require("devtools/server/main");
  35. const { DebuggerClient, ObjectClient } = require("devtools/shared/client/main");
  36. const { MemoryFront } = require("devtools/shared/fronts/memory");
  37. const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
  38. const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
  39. var loadSubScript = Cc[
  40. "@mozilla.org/moz/jssubscript-loader;1"
  41. ].getService(Ci.mozIJSSubScriptLoader).loadSubScript;
  42. /**
  43. * Initializes any test that needs to work with add-ons.
  44. */
  45. function startupAddonsManager() {
  46. // Create a directory for extensions.
  47. const profileDir = do_get_profile().clone();
  48. profileDir.append("extensions");
  49. const internalManager = Cc["@mozilla.org/addons/integration;1"]
  50. .getService(Ci.nsIObserver)
  51. .QueryInterface(Ci.nsITimerCallback);
  52. internalManager.observe(null, "addons-startup", null);
  53. }
  54. /**
  55. * Create a `run_test` function that runs the given generator in a task after
  56. * having attached to a memory actor. When done, the memory actor is detached
  57. * from, the client is finished, and the test is finished.
  58. *
  59. * @param {GeneratorFunction} testGeneratorFunction
  60. * The generator function is passed (DebuggerClient, MemoryFront)
  61. * arguments.
  62. *
  63. * @returns `run_test` function
  64. */
  65. function makeMemoryActorTest(testGeneratorFunction) {
  66. const TEST_GLOBAL_NAME = "test_MemoryActor";
  67. return function run_test() {
  68. do_test_pending();
  69. startTestDebuggerServer(TEST_GLOBAL_NAME).then(client => {
  70. DebuggerServer.registerModule("devtools/server/actors/heap-snapshot-file", {
  71. prefix: "heapSnapshotFile",
  72. constructor: "HeapSnapshotFileActor",
  73. type: { global: true }
  74. });
  75. getTestTab(client, TEST_GLOBAL_NAME, function (tabForm, rootForm) {
  76. if (!tabForm || !rootForm) {
  77. ok(false, "Could not attach to test tab: " + TEST_GLOBAL_NAME);
  78. return;
  79. }
  80. Task.spawn(function* () {
  81. try {
  82. const memoryFront = new MemoryFront(client, tabForm, rootForm);
  83. yield memoryFront.attach();
  84. yield* testGeneratorFunction(client, memoryFront);
  85. yield memoryFront.detach();
  86. } catch (err) {
  87. DevToolsUtils.reportException("makeMemoryActorTest", err);
  88. ok(false, "Got an error: " + err);
  89. }
  90. finishClient(client);
  91. });
  92. });
  93. });
  94. };
  95. }
  96. /**
  97. * Save as makeMemoryActorTest but attaches the MemoryFront to the MemoryActor
  98. * scoped to the full runtime rather than to a tab.
  99. */
  100. function makeFullRuntimeMemoryActorTest(testGeneratorFunction) {
  101. return function run_test() {
  102. do_test_pending();
  103. startTestDebuggerServer("test_MemoryActor").then(client => {
  104. DebuggerServer.registerModule("devtools/server/actors/heap-snapshot-file", {
  105. prefix: "heapSnapshotFile",
  106. constructor: "HeapSnapshotFileActor",
  107. type: { global: true }
  108. });
  109. getChromeActors(client).then(function (form) {
  110. if (!form) {
  111. ok(false, "Could not attach to chrome actors");
  112. return;
  113. }
  114. Task.spawn(function* () {
  115. try {
  116. const rootForm = yield listTabs(client);
  117. const memoryFront = new MemoryFront(client, form, rootForm);
  118. yield memoryFront.attach();
  119. yield* testGeneratorFunction(client, memoryFront);
  120. yield memoryFront.detach();
  121. } catch (err) {
  122. DevToolsUtils.reportException("makeMemoryActorTest", err);
  123. ok(false, "Got an error: " + err);
  124. }
  125. finishClient(client);
  126. });
  127. });
  128. });
  129. };
  130. }
  131. function createTestGlobal(name) {
  132. let sandbox = Cu.Sandbox(
  133. Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
  134. );
  135. sandbox.__name = name;
  136. return sandbox;
  137. }
  138. function connect(client) {
  139. dump("Connecting client.\n");
  140. return client.connect();
  141. }
  142. function close(client) {
  143. dump("Closing client.\n");
  144. return client.close();
  145. }
  146. function listTabs(client) {
  147. dump("Listing tabs.\n");
  148. return client.listTabs();
  149. }
  150. function findTab(tabs, title) {
  151. dump("Finding tab with title '" + title + "'.\n");
  152. for (let tab of tabs) {
  153. if (tab.title === title) {
  154. return tab;
  155. }
  156. }
  157. return null;
  158. }
  159. function attachTab(client, tab) {
  160. dump("Attaching to tab with title '" + tab.title + "'.\n");
  161. return client.attachTab(tab.actor);
  162. }
  163. function waitForNewSource(threadClient, url) {
  164. dump("Waiting for new source with url '" + url + "'.\n");
  165. return waitForEvent(threadClient, "newSource", function (packet) {
  166. return packet.source.url === url;
  167. });
  168. }
  169. function attachThread(tabClient, options = {}) {
  170. dump("Attaching to thread.\n");
  171. return tabClient.attachThread(options);
  172. }
  173. function resume(threadClient) {
  174. dump("Resuming thread.\n");
  175. return threadClient.resume();
  176. }
  177. function getSources(threadClient) {
  178. dump("Getting sources.\n");
  179. return threadClient.getSources();
  180. }
  181. function findSource(sources, url) {
  182. dump("Finding source with url '" + url + "'.\n");
  183. for (let source of sources) {
  184. if (source.url === url) {
  185. return source;
  186. }
  187. }
  188. return null;
  189. }
  190. function waitForPause(threadClient) {
  191. dump("Waiting for pause.\n");
  192. return waitForEvent(threadClient, "paused");
  193. }
  194. function setBreakpoint(sourceClient, location) {
  195. dump("Setting breakpoint.\n");
  196. return sourceClient.setBreakpoint(location);
  197. }
  198. function dumpn(msg) {
  199. dump("DBG-TEST: " + msg + "\n");
  200. }
  201. function testExceptionHook(ex) {
  202. try {
  203. do_report_unexpected_exception(ex);
  204. } catch (ex) {
  205. return {throw: ex};
  206. }
  207. return undefined;
  208. }
  209. // Convert an nsIScriptError 'aFlags' value into an appropriate string.
  210. function scriptErrorFlagsToKind(aFlags) {
  211. var kind;
  212. if (aFlags & Ci.nsIScriptError.warningFlag)
  213. kind = "warning";
  214. if (aFlags & Ci.nsIScriptError.exceptionFlag)
  215. kind = "exception";
  216. else
  217. kind = "error";
  218. if (aFlags & Ci.nsIScriptError.strictFlag)
  219. kind = "strict " + kind;
  220. return kind;
  221. }
  222. // Register a console listener, so console messages don't just disappear
  223. // into the ether.
  224. var errorCount = 0;
  225. var listener = {
  226. observe: function (aMessage) {
  227. try {
  228. errorCount++;
  229. try {
  230. // If we've been given an nsIScriptError, then we can print out
  231. // something nicely formatted, for tools like Emacs to pick up.
  232. var scriptError = aMessage.QueryInterface(Ci.nsIScriptError);
  233. dumpn(aMessage.sourceName + ":" + aMessage.lineNumber + ": " +
  234. scriptErrorFlagsToKind(aMessage.flags) + ": " +
  235. aMessage.errorMessage);
  236. var string = aMessage.errorMessage;
  237. } catch (x) {
  238. // Be a little paranoid with message, as the whole goal here is to lose
  239. // no information.
  240. try {
  241. var string = "" + aMessage.message;
  242. } catch (x) {
  243. var string = "<error converting error message to string>";
  244. }
  245. }
  246. // Make sure we exit all nested event loops so that the test can finish.
  247. while (DebuggerServer
  248. && DebuggerServer.xpcInspector
  249. && DebuggerServer.xpcInspector.eventLoopNestLevel > 0) {
  250. DebuggerServer.xpcInspector.exitNestedEventLoop();
  251. }
  252. // In the world before bug 997440, exceptions were getting lost because of
  253. // the arbitrary JSContext being used in nsXPCWrappedJSClass::CallMethod.
  254. // In the new world, the wanderers have returned. However, because of the,
  255. // currently very-broken, exception reporting machinery in
  256. // XPCWrappedJSClass these get reported as errors to the console, even if
  257. // there's actually JS on the stack above that will catch them. If we
  258. // throw an error here because of them our tests start failing. So, we'll
  259. // just dump the message to the logs instead, to make sure the information
  260. // isn't lost.
  261. dumpn("head_dbg.js observed a console message: " + string);
  262. } catch (_) {
  263. // Swallow everything to avoid console reentrancy errors. We did our best
  264. // to log above, but apparently that didn't cut it.
  265. }
  266. }
  267. };
  268. var consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
  269. consoleService.registerListener(listener);
  270. function check_except(func)
  271. {
  272. try {
  273. func();
  274. } catch (e) {
  275. do_check_true(true);
  276. return;
  277. }
  278. dumpn("Should have thrown an exception: " + func.toString());
  279. do_check_true(false);
  280. }
  281. function testGlobal(aName) {
  282. let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
  283. .createInstance(Ci.nsIPrincipal);
  284. let sandbox = Cu.Sandbox(systemPrincipal);
  285. sandbox.__name = aName;
  286. return sandbox;
  287. }
  288. function addTestGlobal(aName, aServer = DebuggerServer)
  289. {
  290. let global = testGlobal(aName);
  291. aServer.addTestGlobal(global);
  292. return global;
  293. }
  294. // List the DebuggerClient |aClient|'s tabs, look for one whose title is
  295. // |aTitle|, and apply |aCallback| to the packet's entry for that tab.
  296. function getTestTab(aClient, aTitle, aCallback) {
  297. aClient.listTabs(function (aResponse) {
  298. for (let tab of aResponse.tabs) {
  299. if (tab.title === aTitle) {
  300. aCallback(tab, aResponse);
  301. return;
  302. }
  303. }
  304. aCallback(null);
  305. });
  306. }
  307. // Attach to |aClient|'s tab whose title is |aTitle|; pass |aCallback| the
  308. // response packet and a TabClient instance referring to that tab.
  309. function attachTestTab(aClient, aTitle, aCallback) {
  310. getTestTab(aClient, aTitle, function (aTab) {
  311. aClient.attachTab(aTab.actor, aCallback);
  312. });
  313. }
  314. // Attach to |aClient|'s tab whose title is |aTitle|, and then attach to
  315. // that tab's thread. Pass |aCallback| the thread attach response packet, a
  316. // TabClient referring to the tab, and a ThreadClient referring to the
  317. // thread.
  318. function attachTestThread(aClient, aTitle, aCallback) {
  319. attachTestTab(aClient, aTitle, function (aTabResponse, aTabClient) {
  320. function onAttach(aResponse, aThreadClient) {
  321. aCallback(aResponse, aTabClient, aThreadClient, aTabResponse);
  322. }
  323. aTabClient.attachThread({
  324. useSourceMaps: true,
  325. autoBlackBox: true
  326. }, onAttach);
  327. });
  328. }
  329. // Attach to |aClient|'s tab whose title is |aTitle|, attach to the tab's
  330. // thread, and then resume it. Pass |aCallback| the thread's response to
  331. // the 'resume' packet, a TabClient for the tab, and a ThreadClient for the
  332. // thread.
  333. function attachTestTabAndResume(aClient, aTitle, aCallback = () => {}) {
  334. return new Promise((resolve, reject) => {
  335. attachTestThread(aClient, aTitle, function (aResponse, aTabClient, aThreadClient) {
  336. aThreadClient.resume(function (aResponse) {
  337. aCallback(aResponse, aTabClient, aThreadClient);
  338. resolve([aResponse, aTabClient, aThreadClient]);
  339. });
  340. });
  341. });
  342. }
  343. /**
  344. * Initialize the testing debugger server.
  345. */
  346. function initTestDebuggerServer(aServer = DebuggerServer)
  347. {
  348. aServer.registerModule("xpcshell-test/testactors");
  349. // Allow incoming connections.
  350. aServer.init(function () { return true; });
  351. }
  352. /**
  353. * Initialize the testing debugger server with a tab whose title is |title|.
  354. */
  355. function startTestDebuggerServer(title, server = DebuggerServer) {
  356. initTestDebuggerServer(server);
  357. addTestGlobal(title);
  358. DebuggerServer.addTabActors();
  359. let transport = DebuggerServer.connectPipe();
  360. let client = new DebuggerClient(transport);
  361. return connect(client).then(() => client);
  362. }
  363. function finishClient(aClient)
  364. {
  365. aClient.close(function () {
  366. DebuggerServer.destroy();
  367. do_test_finished();
  368. });
  369. }
  370. // Create a server, connect to it and fetch tab actors for the parent process;
  371. // pass |aCallback| the debugger client and tab actor form with all actor IDs.
  372. function get_chrome_actors(callback)
  373. {
  374. if (!DebuggerServer.initialized) {
  375. DebuggerServer.init();
  376. DebuggerServer.addBrowserActors();
  377. }
  378. DebuggerServer.allowChromeProcess = true;
  379. let client = new DebuggerClient(DebuggerServer.connectPipe());
  380. client.connect()
  381. .then(() => client.getProcess())
  382. .then(response => {
  383. callback(client, response.form);
  384. });
  385. }
  386. function getChromeActors(client, server = DebuggerServer) {
  387. server.allowChromeProcess = true;
  388. return client.getProcess().then(response => response.form);
  389. }
  390. /**
  391. * Takes a relative file path and returns the absolute file url for it.
  392. */
  393. function getFileUrl(aName, aAllowMissing = false) {
  394. let file = do_get_file(aName, aAllowMissing);
  395. return Services.io.newFileURI(file).spec;
  396. }
  397. /**
  398. * Returns the full path of the file with the specified name in a
  399. * platform-independent and URL-like form.
  400. */
  401. function getFilePath(aName, aAllowMissing = false, aUsePlatformPathSeparator = false)
  402. {
  403. let file = do_get_file(aName, aAllowMissing);
  404. let path = Services.io.newFileURI(file).spec;
  405. let filePrePath = "file://";
  406. if ("nsILocalFileWin" in Ci &&
  407. file instanceof Ci.nsILocalFileWin) {
  408. filePrePath += "/";
  409. }
  410. path = path.slice(filePrePath.length);
  411. if (aUsePlatformPathSeparator && path.match(/^\w:/)) {
  412. path = path.replace(/\//g, "\\");
  413. }
  414. return path;
  415. }
  416. /**
  417. * Returns the full text contents of the given file.
  418. */
  419. function readFile(aFileName) {
  420. let f = do_get_file(aFileName);
  421. let s = Cc["@mozilla.org/network/file-input-stream;1"]
  422. .createInstance(Ci.nsIFileInputStream);
  423. s.init(f, -1, -1, false);
  424. try {
  425. return NetUtil.readInputStreamToString(s, s.available());
  426. } finally {
  427. s.close();
  428. }
  429. }
  430. function writeFile(aFileName, aContent) {
  431. let file = do_get_file(aFileName, true);
  432. let stream = Cc["@mozilla.org/network/file-output-stream;1"]
  433. .createInstance(Ci.nsIFileOutputStream);
  434. stream.init(file, -1, -1, 0);
  435. try {
  436. do {
  437. let numWritten = stream.write(aContent, aContent.length);
  438. aContent = aContent.slice(numWritten);
  439. } while (aContent.length > 0);
  440. } finally {
  441. stream.close();
  442. }
  443. }
  444. function connectPipeTracing() {
  445. return new TracingTransport(DebuggerServer.connectPipe());
  446. }
  447. function TracingTransport(childTransport) {
  448. this.hooks = null;
  449. this.child = childTransport;
  450. this.child.hooks = this;
  451. this.expectations = [];
  452. this.packets = [];
  453. this.checkIndex = 0;
  454. }
  455. TracingTransport.prototype = {
  456. // Remove actor names
  457. normalize: function (packet) {
  458. return JSON.parse(JSON.stringify(packet, (key, value) => {
  459. if (key === "to" || key === "from" || key === "actor") {
  460. return "<actorid>";
  461. }
  462. return value;
  463. }));
  464. },
  465. send: function (packet) {
  466. this.packets.push({
  467. type: "sent",
  468. packet: this.normalize(packet)
  469. });
  470. return this.child.send(packet);
  471. },
  472. close: function () {
  473. return this.child.close();
  474. },
  475. ready: function () {
  476. return this.child.ready();
  477. },
  478. onPacket: function (packet) {
  479. this.packets.push({
  480. type: "received",
  481. packet: this.normalize(packet)
  482. });
  483. this.hooks.onPacket(packet);
  484. },
  485. onClosed: function () {
  486. this.hooks.onClosed();
  487. },
  488. expectSend: function (expected) {
  489. let packet = this.packets[this.checkIndex++];
  490. do_check_eq(packet.type, "sent");
  491. deepEqual(packet.packet, this.normalize(expected));
  492. },
  493. expectReceive: function (expected) {
  494. let packet = this.packets[this.checkIndex++];
  495. do_check_eq(packet.type, "received");
  496. deepEqual(packet.packet, this.normalize(expected));
  497. },
  498. // Write your tests, call dumpLog at the end, inspect the output,
  499. // then sprinkle the calls through the right places in your test.
  500. dumpLog: function () {
  501. for (let entry of this.packets) {
  502. if (entry.type === "sent") {
  503. dumpn("trace.expectSend(" + entry.packet + ");");
  504. } else {
  505. dumpn("trace.expectReceive(" + entry.packet + ");");
  506. }
  507. }
  508. }
  509. };
  510. function StubTransport() { }
  511. StubTransport.prototype.ready = function () {};
  512. StubTransport.prototype.send = function () {};
  513. StubTransport.prototype.close = function () {};
  514. function executeSoon(aFunc) {
  515. Services.tm.mainThread.dispatch({
  516. run: DevToolsUtils.makeInfallible(aFunc)
  517. }, Ci.nsIThread.DISPATCH_NORMAL);
  518. }
  519. // The do_check_* family of functions expect their last argument to be an
  520. // optional stack object. Unfortunately, most tests actually pass a in a string
  521. // containing an error message instead, which causes error reporting to break if
  522. // strict warnings as errors is turned on. To avoid this, we wrap these
  523. // functions here below to ensure the correct number of arguments is passed.
  524. //
  525. // TODO: Remove this once bug 906232 is resolved
  526. //
  527. var do_check_true_old = do_check_true;
  528. var do_check_true = function (condition) {
  529. do_check_true_old(condition);
  530. };
  531. var do_check_false_old = do_check_false;
  532. var do_check_false = function (condition) {
  533. do_check_false_old(condition);
  534. };
  535. var do_check_eq_old = do_check_eq;
  536. var do_check_eq = function (left, right) {
  537. do_check_eq_old(left, right);
  538. };
  539. var do_check_neq_old = do_check_neq;
  540. var do_check_neq = function (left, right) {
  541. do_check_neq_old(left, right);
  542. };
  543. var do_check_matches_old = do_check_matches;
  544. var do_check_matches = function (pattern, value) {
  545. do_check_matches_old(pattern, value);
  546. };
  547. // Create async version of the object where calling each method
  548. // is equivalent of calling it with asyncall. Mainly useful for
  549. // destructuring objects with methods that take callbacks.
  550. const Async = target => new Proxy(target, Async);
  551. Async.get = (target, name) =>
  552. typeof (target[name]) === "function" ? asyncall.bind(null, target[name], target) :
  553. target[name];
  554. // Calls async function that takes callback and errorback and returns
  555. // returns promise representing result.
  556. const asyncall = (fn, self, ...args) =>
  557. new Promise((...etc) => fn.call(self, ...args, ...etc));
  558. const Test = task => () => {
  559. add_task(task);
  560. run_next_test();
  561. };
  562. const assert = do_check_true;
  563. /**
  564. * Create a promise that is resolved on the next occurence of the given event.
  565. *
  566. * @param DebuggerClient client
  567. * @param String event
  568. * @param Function predicate
  569. * @returns Promise
  570. */
  571. function waitForEvent(client, type, predicate) {
  572. return new Promise(function (resolve) {
  573. function listener(type, packet) {
  574. if (!predicate(packet)) {
  575. return;
  576. }
  577. client.removeListener(listener);
  578. resolve(packet);
  579. }
  580. if (predicate) {
  581. client.addListener(type, listener);
  582. } else {
  583. client.addOneTimeListener(type, function (type, packet) {
  584. resolve(packet);
  585. });
  586. }
  587. });
  588. }
  589. /**
  590. * Execute the action on the next tick and return a promise that is resolved on
  591. * the next pause.
  592. *
  593. * When using promises and Task.jsm, we often want to do an action that causes a
  594. * pause and continue the task once the pause has ocurred. Unfortunately, if we
  595. * do the action that causes the pause within the task's current tick we will
  596. * pause before we have a chance to yield the promise that waits for the pause
  597. * and we enter a dead lock. The solution is to create the promise that waits
  598. * for the pause, schedule the action to run on the next tick of the event loop,
  599. * and finally yield the promise.
  600. *
  601. * @param Function action
  602. * @param DebuggerClient client
  603. * @returns Promise
  604. */
  605. function executeOnNextTickAndWaitForPause(action, client) {
  606. const paused = waitForPause(client);
  607. executeSoon(action);
  608. return paused;
  609. }
  610. /**
  611. * Interrupt JS execution for the specified thread.
  612. *
  613. * @param ThreadClient threadClient
  614. * @returns Promise
  615. */
  616. function interrupt(threadClient) {
  617. dumpn("Interrupting.");
  618. return threadClient.interrupt();
  619. }
  620. /**
  621. * Resume JS execution for the specified thread and then wait for the next pause
  622. * event.
  623. *
  624. * @param DebuggerClient client
  625. * @param ThreadClient threadClient
  626. * @returns Promise
  627. */
  628. function resumeAndWaitForPause(client, threadClient) {
  629. const paused = waitForPause(client);
  630. return resume(threadClient).then(() => paused);
  631. }
  632. /**
  633. * Resume JS execution for a single step and wait for the pause after the step
  634. * has been taken.
  635. *
  636. * @param DebuggerClient client
  637. * @param ThreadClient threadClient
  638. * @returns Promise
  639. */
  640. function stepIn(client, threadClient) {
  641. dumpn("Stepping in.");
  642. const paused = waitForPause(client);
  643. return threadClient.stepIn()
  644. .then(() => paused);
  645. }
  646. /**
  647. * Resume JS execution for a step over and wait for the pause after the step
  648. * has been taken.
  649. *
  650. * @param DebuggerClient client
  651. * @param ThreadClient threadClient
  652. * @returns Promise
  653. */
  654. function stepOver(client, threadClient) {
  655. dumpn("Stepping over.");
  656. return threadClient.stepOver()
  657. .then(() => waitForPause(client));
  658. }
  659. /**
  660. * Get the list of `count` frames currently on stack, starting at the index
  661. * `first` for the specified thread.
  662. *
  663. * @param ThreadClient threadClient
  664. * @param Number first
  665. * @param Number count
  666. * @returns Promise
  667. */
  668. function getFrames(threadClient, first, count) {
  669. dumpn("Getting frames.");
  670. return threadClient.getFrames(first, count);
  671. }
  672. /**
  673. * Black box the specified source.
  674. *
  675. * @param SourceClient sourceClient
  676. * @returns Promise
  677. */
  678. function blackBox(sourceClient) {
  679. dumpn("Black boxing source: " + sourceClient.actor);
  680. return sourceClient.blackBox();
  681. }
  682. /**
  683. * Stop black boxing the specified source.
  684. *
  685. * @param SourceClient sourceClient
  686. * @returns Promise
  687. */
  688. function unBlackBox(sourceClient) {
  689. dumpn("Un-black boxing source: " + sourceClient.actor);
  690. return sourceClient.unblackBox();
  691. }
  692. /**
  693. * Perform a "source" RDP request with the given SourceClient to get the source
  694. * content and content type.
  695. *
  696. * @param SourceClient sourceClient
  697. * @returns Promise
  698. */
  699. function getSourceContent(sourceClient) {
  700. dumpn("Getting source content for " + sourceClient.actor);
  701. return sourceClient.source();
  702. }
  703. /**
  704. * Get a source at the specified url.
  705. *
  706. * @param ThreadClient threadClient
  707. * @param string url
  708. * @returns Promise<SourceClient>
  709. */
  710. function getSource(threadClient, url) {
  711. let deferred = promise.defer();
  712. threadClient.getSources((res) => {
  713. let source = res.sources.filter(function (s) {
  714. return s.url === url;
  715. });
  716. if (source.length) {
  717. deferred.resolve(threadClient.source(source[0]));
  718. }
  719. else {
  720. deferred.reject(new Error("source not found"));
  721. }
  722. });
  723. return deferred.promise;
  724. }
  725. /**
  726. * Do a fake reload which clears the thread debugger
  727. *
  728. * @param TabClient tabClient
  729. * @returns Promise<response>
  730. */
  731. function reload(tabClient) {
  732. let deferred = promise.defer();
  733. tabClient._reload({}, deferred.resolve);
  734. return deferred.promise;
  735. }
  736. /**
  737. * Returns an array of stack location strings given a thread and a sample.
  738. *
  739. * @param object thread
  740. * @param object sample
  741. * @returns object
  742. */
  743. function getInflatedStackLocations(thread, sample) {
  744. let stackTable = thread.stackTable;
  745. let frameTable = thread.frameTable;
  746. let stringTable = thread.stringTable;
  747. let SAMPLE_STACK_SLOT = thread.samples.schema.stack;
  748. let STACK_PREFIX_SLOT = stackTable.schema.prefix;
  749. let STACK_FRAME_SLOT = stackTable.schema.frame;
  750. let FRAME_LOCATION_SLOT = frameTable.schema.location;
  751. // Build the stack from the raw data and accumulate the locations in
  752. // an array.
  753. let stackIndex = sample[SAMPLE_STACK_SLOT];
  754. let locations = [];
  755. while (stackIndex !== null) {
  756. let stackEntry = stackTable.data[stackIndex];
  757. let frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]];
  758. locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]);
  759. stackIndex = stackEntry[STACK_PREFIX_SLOT];
  760. }
  761. // The profiler tree is inverted, so reverse the array.
  762. return locations.reverse();
  763. }