head.js 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
  2. /* Any copyright is dedicated to the Public Domain.
  3. * http://creativecommons.org/publicdomain/zero/1.0/ */
  4. "use strict";
  5. // shared-head.js handles imports, constants, and utility functions
  6. Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
  7. // Disable logging for faster test runs. Set this pref to true if you want to
  8. // debug a test in your try runs. Both the debugger server and frontend will
  9. // be affected by this pref.
  10. var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
  11. Services.prefs.setBoolPref("devtools.debugger.log", false);
  12. var { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
  13. var { DebuggerServer } = require("devtools/server/main");
  14. var { DebuggerClient, ObjectClient } = require("devtools/shared/client/main");
  15. var { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
  16. var EventEmitter = require("devtools/shared/event-emitter");
  17. var { Toolbox } = require("devtools/client/framework/toolbox");
  18. const chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry);
  19. // Override promise with deprecated-sync-thenables
  20. promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
  21. const EXAMPLE_URL = "http://example.com/browser/devtools/client/debugger/test/mochitest/";
  22. const FRAME_SCRIPT_URL = getRootDirectory(gTestPath) + "code_frame-script.js";
  23. const CHROME_URL = "chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/";
  24. const CHROME_URI = Services.io.newURI(CHROME_URL, null, null);
  25. Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
  26. registerCleanupFunction(function* () {
  27. Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
  28. info("finish() was called, cleaning up...");
  29. Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
  30. while (gBrowser && gBrowser.tabs && gBrowser.tabs.length > 1) {
  31. info("Destroying toolbox.");
  32. let target = TargetFactory.forTab(gBrowser.selectedTab);
  33. yield gDevTools.closeToolbox(target);
  34. info("Removing tab.");
  35. gBrowser.removeCurrentTab();
  36. }
  37. // Properly shut down the server to avoid memory leaks.
  38. DebuggerServer.destroy();
  39. // Debugger tests use a lot of memory, so force a GC to help fragmentation.
  40. info("Forcing GC after debugger test.");
  41. Cu.forceGC();
  42. });
  43. // Import the GCLI test helper
  44. var testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
  45. testDir = testDir.replace(/\/\//g, "/");
  46. testDir = testDir.replace("chrome:/mochitest", "chrome://mochitest");
  47. var helpersjs = testDir + "/../../../commandline/test/helpers.js";
  48. Services.scriptloader.loadSubScript(helpersjs, this);
  49. function addWindow(aUrl) {
  50. info("Adding window: " + aUrl);
  51. return promise.resolve(getChromeWindow(window.open(aUrl)));
  52. }
  53. function getChromeWindow(aWindow) {
  54. return aWindow
  55. .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
  56. .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
  57. .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
  58. }
  59. // Override addTab/removeTab as defined by shared-head, since these have
  60. // an extra window parameter and add a frame script
  61. this.addTab = function addTab(aUrl, aWindow) {
  62. info("Adding tab: " + aUrl);
  63. let deferred = promise.defer();
  64. let targetWindow = aWindow || window;
  65. let targetBrowser = targetWindow.gBrowser;
  66. targetWindow.focus();
  67. let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
  68. let linkedBrowser = tab.linkedBrowser;
  69. info("Loading frame script with url " + FRAME_SCRIPT_URL + ".");
  70. linkedBrowser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
  71. BrowserTestUtils.browserLoaded(linkedBrowser)
  72. .then(function () {
  73. info("Tab added and finished loading: " + aUrl);
  74. deferred.resolve(tab);
  75. });
  76. return deferred.promise;
  77. };
  78. this.removeTab = function removeTab(aTab, aWindow) {
  79. info("Removing tab.");
  80. let deferred = promise.defer();
  81. let targetWindow = aWindow || window;
  82. let targetBrowser = targetWindow.gBrowser;
  83. let tabContainer = targetBrowser.tabContainer;
  84. tabContainer.addEventListener("TabClose", function onClose(aEvent) {
  85. tabContainer.removeEventListener("TabClose", onClose, false);
  86. info("Tab removed and finished closing.");
  87. deferred.resolve();
  88. }, false);
  89. targetBrowser.removeTab(aTab);
  90. return deferred.promise;
  91. };
  92. function getAddonURIFromPath(aPath) {
  93. let chromeURI = Services.io.newURI(aPath, null, CHROME_URI);
  94. return chromeRegistry.convertChromeURL(chromeURI).QueryInterface(Ci.nsIFileURL);
  95. }
  96. function getTemporaryAddonURLFromPath(aPath) {
  97. return getAddonURIFromPath(aPath).spec;
  98. }
  99. function addTemporaryAddon(aPath) {
  100. let addonFile = getAddonURIFromPath(aPath).file;
  101. info("Installing addon: " + addonFile.path);
  102. return AddonManager.installTemporaryAddon(addonFile);
  103. }
  104. function removeAddon(aAddon) {
  105. info("Removing addon.");
  106. let deferred = promise.defer();
  107. let listener = {
  108. onUninstalled: function (aUninstalledAddon) {
  109. if (aUninstalledAddon != aAddon) {
  110. return;
  111. }
  112. AddonManager.removeAddonListener(listener);
  113. deferred.resolve();
  114. }
  115. };
  116. AddonManager.addAddonListener(listener);
  117. aAddon.uninstall();
  118. return deferred.promise;
  119. }
  120. function getTabActorForUrl(aClient, aUrl) {
  121. let deferred = promise.defer();
  122. aClient.listTabs(aResponse => {
  123. let tabActor = aResponse.tabs.filter(aGrip => aGrip.url == aUrl).pop();
  124. deferred.resolve(tabActor);
  125. });
  126. return deferred.promise;
  127. }
  128. function getAddonActorForId(aClient, aAddonId) {
  129. info("Get addon actor for ID: " + aAddonId);
  130. let deferred = promise.defer();
  131. aClient.listAddons(aResponse => {
  132. let addonActor = aResponse.addons.filter(aGrip => aGrip.id == aAddonId).pop();
  133. info("got addon actor for ID: " + aAddonId);
  134. deferred.resolve(addonActor);
  135. });
  136. return deferred.promise;
  137. }
  138. function attachTabActorForUrl(aClient, aUrl) {
  139. let deferred = promise.defer();
  140. getTabActorForUrl(aClient, aUrl).then(aGrip => {
  141. aClient.attachTab(aGrip.actor, aResponse => {
  142. deferred.resolve([aGrip, aResponse]);
  143. });
  144. });
  145. return deferred.promise;
  146. }
  147. function attachThreadActorForUrl(aClient, aUrl) {
  148. let deferred = promise.defer();
  149. attachTabActorForUrl(aClient, aUrl).then(([aGrip, aResponse]) => {
  150. aClient.attachThread(aResponse.threadActor, (aResponse, aThreadClient) => {
  151. aThreadClient.resume(aResponse => {
  152. deferred.resolve(aThreadClient);
  153. });
  154. });
  155. });
  156. return deferred.promise;
  157. }
  158. function once(aTarget, aEventName, aUseCapture = false) {
  159. info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
  160. let deferred = promise.defer();
  161. for (let [add, remove] of [
  162. ["addEventListener", "removeEventListener"],
  163. ["addListener", "removeListener"],
  164. ["on", "off"]
  165. ]) {
  166. if ((add in aTarget) && (remove in aTarget)) {
  167. aTarget[add](aEventName, function onEvent(...aArgs) {
  168. aTarget[remove](aEventName, onEvent, aUseCapture);
  169. deferred.resolve.apply(deferred, aArgs);
  170. }, aUseCapture);
  171. break;
  172. }
  173. }
  174. return deferred.promise;
  175. }
  176. function waitForTick() {
  177. let deferred = promise.defer();
  178. executeSoon(deferred.resolve);
  179. return deferred.promise;
  180. }
  181. function waitForTime(aDelay) {
  182. let deferred = promise.defer();
  183. setTimeout(deferred.resolve, aDelay);
  184. return deferred.promise;
  185. }
  186. function waitForSourceLoaded(aPanel, aUrl) {
  187. let { Sources } = aPanel.panelWin.DebuggerView;
  188. let isLoaded = Sources.items.some(item =>
  189. item.attachment.source.url === aUrl);
  190. if (isLoaded) {
  191. info("The correct source has been loaded.");
  192. return promise.resolve(null);
  193. } else {
  194. return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.NEW_SOURCE).then(() => {
  195. // Wait for it to be loaded in the UI and appear into Sources.items.
  196. return waitForTick();
  197. }).then(() => {
  198. return waitForSourceLoaded(aPanel, aUrl);
  199. });
  200. }
  201. }
  202. function waitForSourceShown(aPanel, aUrl) {
  203. return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.SOURCE_SHOWN).then(aSource => {
  204. let sourceUrl = aSource.url || aSource.introductionUrl;
  205. info("Source shown: " + sourceUrl);
  206. if (!sourceUrl.includes(aUrl)) {
  207. return waitForSourceShown(aPanel, aUrl);
  208. } else {
  209. ok(true, "The correct source has been shown.");
  210. }
  211. });
  212. }
  213. function waitForEditorLocationSet(aPanel) {
  214. return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.EDITOR_LOCATION_SET);
  215. }
  216. function ensureSourceIs(aPanel, aUrlOrSource, aWaitFlag = false) {
  217. let sources = aPanel.panelWin.DebuggerView.Sources;
  218. if (sources.selectedValue === aUrlOrSource ||
  219. (sources.selectedItem &&
  220. sources.selectedItem.attachment.source.url.includes(aUrlOrSource))) {
  221. ok(true, "Expected source is shown: " + aUrlOrSource);
  222. return promise.resolve(null);
  223. }
  224. if (aWaitFlag) {
  225. return waitForSourceShown(aPanel, aUrlOrSource);
  226. }
  227. ok(false, "Expected source was not already shown: " + aUrlOrSource);
  228. return promise.reject(null);
  229. }
  230. function waitForCaretUpdated(aPanel, aLine, aCol = 1) {
  231. return waitForEditorEvents(aPanel, "cursorActivity").then(() => {
  232. let cursor = aPanel.panelWin.DebuggerView.editor.getCursor();
  233. info("Caret updated: " + (cursor.line + 1) + ", " + (cursor.ch + 1));
  234. if (!isCaretPos(aPanel, aLine, aCol)) {
  235. return waitForCaretUpdated(aPanel, aLine, aCol);
  236. } else {
  237. ok(true, "The correct caret position has been set.");
  238. }
  239. });
  240. }
  241. function ensureCaretAt(aPanel, aLine, aCol = 1, aWaitFlag = false) {
  242. if (isCaretPos(aPanel, aLine, aCol)) {
  243. ok(true, "Expected caret position is set: " + aLine + "," + aCol);
  244. return promise.resolve(null);
  245. }
  246. if (aWaitFlag) {
  247. return waitForCaretUpdated(aPanel, aLine, aCol);
  248. }
  249. ok(false, "Expected caret position was not already set: " + aLine + "," + aCol);
  250. return promise.reject(null);
  251. }
  252. function isCaretPos(aPanel, aLine, aCol = 1) {
  253. let editor = aPanel.panelWin.DebuggerView.editor;
  254. let cursor = editor.getCursor();
  255. // Source editor starts counting line and column numbers from 0.
  256. info("Current editor caret position: " + (cursor.line + 1) + ", " + (cursor.ch + 1));
  257. return cursor.line == (aLine - 1) && cursor.ch == (aCol - 1);
  258. }
  259. function isDebugPos(aPanel, aLine) {
  260. let editor = aPanel.panelWin.DebuggerView.editor;
  261. let location = editor.getDebugLocation();
  262. // Source editor starts counting line and column numbers from 0.
  263. info("Current editor debug position: " + (location + 1));
  264. return location != null && editor.hasLineClass(aLine - 1, "debug-line");
  265. }
  266. function isEditorSel(aPanel, [start, end]) {
  267. let editor = aPanel.panelWin.DebuggerView.editor;
  268. let range = {
  269. start: editor.getOffset(editor.getCursor("start")),
  270. end: editor.getOffset(editor.getCursor())
  271. };
  272. // Source editor starts counting line and column numbers from 0.
  273. info("Current editor selection: " + (range.start + 1) + ", " + (range.end + 1));
  274. return range.start == (start - 1) && range.end == (end - 1);
  275. }
  276. function waitForSourceAndCaret(aPanel, aUrl, aLine, aCol) {
  277. return promise.all([
  278. waitForSourceShown(aPanel, aUrl),
  279. waitForCaretUpdated(aPanel, aLine, aCol)
  280. ]);
  281. }
  282. function waitForCaretAndScopes(aPanel, aLine, aCol) {
  283. return promise.all([
  284. waitForCaretUpdated(aPanel, aLine, aCol),
  285. waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.FETCHED_SCOPES)
  286. ]);
  287. }
  288. function waitForSourceAndCaretAndScopes(aPanel, aUrl, aLine, aCol) {
  289. return promise.all([
  290. waitForSourceAndCaret(aPanel, aUrl, aLine, aCol),
  291. waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.FETCHED_SCOPES)
  292. ]);
  293. }
  294. function waitForDebuggerEvents(aPanel, aEventName, aEventRepeat = 1) {
  295. info("Waiting for debugger event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
  296. let deferred = promise.defer();
  297. let panelWin = aPanel.panelWin;
  298. let count = 0;
  299. panelWin.on(aEventName, function onEvent(aEventName, ...aArgs) {
  300. info("Debugger event '" + aEventName + "' fired: " + (++count) + " time(s).");
  301. if (count == aEventRepeat) {
  302. ok(true, "Enough '" + aEventName + "' panel events have been fired.");
  303. panelWin.off(aEventName, onEvent);
  304. deferred.resolve.apply(deferred, aArgs);
  305. }
  306. });
  307. return deferred.promise;
  308. }
  309. function waitForEditorEvents(aPanel, aEventName, aEventRepeat = 1) {
  310. info("Waiting for editor event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
  311. let deferred = promise.defer();
  312. let editor = aPanel.panelWin.DebuggerView.editor;
  313. let count = 0;
  314. editor.on(aEventName, function onEvent(...aArgs) {
  315. info("Editor event '" + aEventName + "' fired: " + (++count) + " time(s).");
  316. if (count == aEventRepeat) {
  317. ok(true, "Enough '" + aEventName + "' editor events have been fired.");
  318. editor.off(aEventName, onEvent);
  319. deferred.resolve.apply(deferred, aArgs);
  320. }
  321. });
  322. return deferred.promise;
  323. }
  324. function waitForThreadEvents(aPanel, aEventName, aEventRepeat = 1) {
  325. info("Waiting for thread event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
  326. let deferred = promise.defer();
  327. let thread = aPanel.panelWin.gThreadClient;
  328. let count = 0;
  329. thread.addListener(aEventName, function onEvent(aEventName, ...aArgs) {
  330. info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s).");
  331. if (count == aEventRepeat) {
  332. ok(true, "Enough '" + aEventName + "' thread events have been fired.");
  333. thread.removeListener(aEventName, onEvent);
  334. deferred.resolve.apply(deferred, aArgs);
  335. }
  336. });
  337. return deferred.promise;
  338. }
  339. function waitForClientEvents(aPanel, aEventName, aEventRepeat = 1) {
  340. info("Waiting for client event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
  341. let deferred = promise.defer();
  342. let client = aPanel.panelWin.gClient;
  343. let count = 0;
  344. client.addListener(aEventName, function onEvent(aEventName, ...aArgs) {
  345. info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s).");
  346. if (count == aEventRepeat) {
  347. ok(true, "Enough '" + aEventName + "' thread events have been fired.");
  348. client.removeListener(aEventName, onEvent);
  349. deferred.resolve.apply(deferred, aArgs);
  350. }
  351. });
  352. return deferred.promise;
  353. }
  354. function ensureThreadClientState(aPanel, aState) {
  355. let thread = aPanel.panelWin.gThreadClient;
  356. let state = thread.state;
  357. info("Thread is: '" + state + "'.");
  358. if (state == aState) {
  359. return promise.resolve(null);
  360. } else {
  361. return waitForThreadEvents(aPanel, aState);
  362. }
  363. }
  364. function reload(aPanel, aUrl) {
  365. let activeTab = aPanel.panelWin.DebuggerController._target.activeTab;
  366. aUrl ? activeTab.navigateTo(aUrl) : activeTab.reload();
  367. }
  368. function navigateActiveTabTo(aPanel, aUrl, aWaitForEventName, aEventRepeat) {
  369. let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat);
  370. reload(aPanel, aUrl);
  371. return finished;
  372. }
  373. function navigateActiveTabInHistory(aPanel, aDirection, aWaitForEventName, aEventRepeat) {
  374. let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat);
  375. content.history[aDirection]();
  376. return finished;
  377. }
  378. function reloadActiveTab(aPanel, aWaitForEventName, aEventRepeat) {
  379. return navigateActiveTabTo(aPanel, null, aWaitForEventName, aEventRepeat);
  380. }
  381. function clearText(aElement) {
  382. info("Clearing text...");
  383. aElement.focus();
  384. aElement.value = "";
  385. }
  386. function setText(aElement, aText) {
  387. clearText(aElement);
  388. info("Setting text: " + aText);
  389. aElement.value = aText;
  390. }
  391. function typeText(aElement, aText) {
  392. info("Typing text: " + aText);
  393. aElement.focus();
  394. EventUtils.sendString(aText, aElement.ownerDocument.defaultView);
  395. }
  396. function backspaceText(aElement, aTimes) {
  397. info("Pressing backspace " + aTimes + " times.");
  398. for (let i = 0; i < aTimes; i++) {
  399. aElement.focus();
  400. EventUtils.sendKey("BACK_SPACE", aElement.ownerDocument.defaultView);
  401. }
  402. }
  403. function getTab(aTarget, aWindow) {
  404. if (aTarget instanceof XULElement) {
  405. return promise.resolve(aTarget);
  406. } else {
  407. return addTab(aTarget, aWindow);
  408. }
  409. }
  410. function getSources(aClient) {
  411. info("Getting sources.");
  412. let deferred = promise.defer();
  413. aClient.getSources((packet) => {
  414. deferred.resolve(packet.sources);
  415. });
  416. return deferred.promise;
  417. }
  418. /**
  419. * Optionaly open a new tab and then open the debugger panel.
  420. * The returned promise resolves only one the panel is fully set.
  421. * @param {String|xul:tab} urlOrTab
  422. * If a string, consider it as the url of the tab to open before opening the
  423. * debugger panel.
  424. * Otherwise, if a <xul:tab>, do nothing, but open the debugger panel against
  425. * the given tab.
  426. * @param {Object} options
  427. * Set of optional arguments:
  428. * - {String} source
  429. * If given, assert the default loaded source once the debugger is loaded.
  430. * This string can be partial to only match a part of the source name.
  431. * If null, do not expect any source and skip SOURCE_SHOWN wait.
  432. * - {Number} line
  433. * If given, wait for the caret to be set on a precise line
  434. *
  435. * @return {Promise}
  436. * Resolves once debugger panel is fully set according to the given options.
  437. */
  438. let initDebugger = Task.async(function*(urlOrTab, options) {
  439. let { window, source, line } = options || {};
  440. info("Initializing a debugger panel.");
  441. let tab, url;
  442. if (urlOrTab instanceof XULElement) {
  443. // `urlOrTab` Is a Tab.
  444. tab = urlOrTab;
  445. } else {
  446. // `urlOrTab` is an url. Open an empty tab first in order to load the page
  447. // only once the panel is ready. That to be able to safely catch the
  448. // SOURCE_SHOWN event.
  449. tab = yield addTab("about:blank", window);
  450. url = urlOrTab;
  451. }
  452. info("Debugee tab added successfully: " + urlOrTab);
  453. let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
  454. let target = TargetFactory.forTab(tab);
  455. let toolbox = yield gDevTools.showToolbox(target, "jsdebugger");
  456. info("Debugger panel shown successfully.");
  457. let debuggerPanel = toolbox.getCurrentPanel();
  458. let panelWin = debuggerPanel.panelWin;
  459. let { Sources } = panelWin.DebuggerView;
  460. prepareDebugger(debuggerPanel);
  461. if (url && url != "about:blank") {
  462. let onCaretUpdated;
  463. if (line) {
  464. onCaretUpdated = waitForCaretUpdated(debuggerPanel, line);
  465. }
  466. if (source === null) {
  467. // When there is no source in the document, we shouldn't wait for
  468. // SOURCE_SHOWN event
  469. yield reload(debuggerPanel, url);
  470. } else {
  471. yield navigateActiveTabTo(debuggerPanel,
  472. url,
  473. panelWin.EVENTS.SOURCE_SHOWN);
  474. }
  475. if (source) {
  476. let isSelected = Sources.selectedItem.attachment.source.url === source;
  477. if (!isSelected) {
  478. // Ensure that the source is loaded first before trying to select it
  479. yield waitForSourceLoaded(debuggerPanel, source);
  480. // Select the js file.
  481. let onSource = waitForSourceAndCaret(debuggerPanel, source, line ? line : 1);
  482. Sources.selectedValue = getSourceActor(Sources, source);
  483. yield onSource;
  484. }
  485. }
  486. yield onCaretUpdated;
  487. }
  488. return [tab, debuggee, debuggerPanel, window];
  489. });
  490. // Creates an add-on debugger for a given add-on. The returned AddonDebugger
  491. // object must be destroyed before finishing the test
  492. function initAddonDebugger(aAddonId) {
  493. let addonDebugger = new AddonDebugger();
  494. return addonDebugger.init(aAddonId).then(() => addonDebugger);
  495. }
  496. function AddonDebugger() {
  497. this._onMessage = this._onMessage.bind(this);
  498. this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
  499. EventEmitter.decorate(this);
  500. }
  501. AddonDebugger.prototype = {
  502. init: Task.async(function* (aAddonId) {
  503. info("Initializing an addon debugger panel.");
  504. if (!DebuggerServer.initialized) {
  505. DebuggerServer.init();
  506. DebuggerServer.addBrowserActors();
  507. }
  508. DebuggerServer.allowChromeProcess = true;
  509. this.frame = document.createElement("iframe");
  510. this.frame.setAttribute("height", 400);
  511. document.documentElement.appendChild(this.frame);
  512. window.addEventListener("message", this._onMessage);
  513. let transport = DebuggerServer.connectPipe();
  514. this.client = new DebuggerClient(transport);
  515. yield this.client.connect();
  516. let addonActor = yield getAddonActorForId(this.client, aAddonId);
  517. let targetOptions = {
  518. form: addonActor,
  519. client: this.client,
  520. chrome: true,
  521. isTabActor: false
  522. };
  523. let toolboxOptions = {
  524. customIframe: this.frame
  525. };
  526. this.target = TargetFactory.forTab(targetOptions);
  527. let toolbox = yield gDevTools.showToolbox(this.target, "jsdebugger", Toolbox.HostType.CUSTOM, toolboxOptions);
  528. info("Addon debugger panel shown successfully.");
  529. this.debuggerPanel = toolbox.getCurrentPanel();
  530. yield waitForSourceShown(this.debuggerPanel, "");
  531. prepareDebugger(this.debuggerPanel);
  532. yield this._attachConsole();
  533. }),
  534. destroy: Task.async(function* () {
  535. yield this.client.close();
  536. yield this.debuggerPanel._toolbox.destroy();
  537. this.frame.remove();
  538. window.removeEventListener("message", this._onMessage);
  539. }),
  540. _attachConsole: function () {
  541. let deferred = promise.defer();
  542. this.client.attachConsole(this.target.form.consoleActor, ["ConsoleAPI"], (aResponse, aWebConsoleClient) => {
  543. if (aResponse.error) {
  544. deferred.reject(aResponse);
  545. }
  546. else {
  547. this.webConsole = aWebConsoleClient;
  548. this.client.addListener("consoleAPICall", this._onConsoleAPICall);
  549. deferred.resolve();
  550. }
  551. });
  552. return deferred.promise;
  553. },
  554. _onConsoleAPICall: function (aType, aPacket) {
  555. if (aPacket.from != this.webConsole.actor)
  556. return;
  557. this.emit("console", aPacket.message);
  558. },
  559. /**
  560. * Returns a list of the groups and sources in the UI. The returned array
  561. * contains objects for each group with properties name and sources. The
  562. * sources property contains an array with objects for each source for that
  563. * group with properties label and url.
  564. */
  565. getSourceGroups: Task.async(function* () {
  566. let debuggerWin = this.debuggerPanel.panelWin;
  567. let sources = yield getSources(debuggerWin.gThreadClient);
  568. ok(sources.length, "retrieved sources");
  569. // groups will be the return value, groupmap and the maps we put in it will
  570. // be used as quick lookups to add the url information in below
  571. let groups = [];
  572. let groupmap = new Map();
  573. let uigroups = this.debuggerPanel.panelWin.document.querySelectorAll(".side-menu-widget-group");
  574. for (let g of uigroups) {
  575. let name = g.querySelector(".side-menu-widget-group-title .name").value;
  576. let group = {
  577. name: name,
  578. sources: []
  579. };
  580. groups.push(group);
  581. let labelmap = new Map();
  582. groupmap.set(name, labelmap);
  583. for (let l of g.querySelectorAll(".dbg-source-item")) {
  584. let source = {
  585. label: l.value,
  586. url: null
  587. };
  588. labelmap.set(l.value, source);
  589. group.sources.push(source);
  590. }
  591. }
  592. for (let source of sources) {
  593. let { label, group } = debuggerWin.DebuggerView.Sources.getItemByValue(source.actor).attachment;
  594. if (!groupmap.has(group)) {
  595. ok(false, "Saw a source group not in the UI: " + group);
  596. continue;
  597. }
  598. if (!groupmap.get(group).has(label)) {
  599. ok(false, "Saw a source label not in the UI: " + label);
  600. continue;
  601. }
  602. groupmap.get(group).get(label).url = source.url.split(" -> ").pop();
  603. }
  604. return groups;
  605. }),
  606. _onMessage: function (event) {
  607. if (typeof(event.data) !== "string") {
  608. return;
  609. }
  610. let json = JSON.parse(event.data);
  611. switch (json.name) {
  612. case "toolbox-title":
  613. this.title = json.data.value;
  614. break;
  615. }
  616. }
  617. };
  618. function initChromeDebugger(aOnClose) {
  619. info("Initializing a chrome debugger process.");
  620. let deferred = promise.defer();
  621. // Wait for the toolbox process to start...
  622. BrowserToolboxProcess.init(aOnClose, (aEvent, aProcess) => {
  623. info("Browser toolbox process started successfully.");
  624. prepareDebugger(aProcess);
  625. deferred.resolve(aProcess);
  626. });
  627. return deferred.promise;
  628. }
  629. function prepareDebugger(aDebugger) {
  630. if ("target" in aDebugger) {
  631. let view = aDebugger.panelWin.DebuggerView;
  632. view.Variables.lazyEmpty = false;
  633. view.Variables.lazySearch = false;
  634. view.Filtering.FilteredSources._autoSelectFirstItem = true;
  635. view.Filtering.FilteredFunctions._autoSelectFirstItem = true;
  636. } else {
  637. // Nothing to do here yet.
  638. }
  639. }
  640. function teardown(aPanel, aFlags = {}) {
  641. info("Destroying the specified debugger.");
  642. let toolbox = aPanel._toolbox;
  643. let tab = aPanel.target.tab;
  644. let debuggerRootActorDisconnected = once(window, "Debugger:Shutdown");
  645. let debuggerPanelDestroyed = once(aPanel, "destroyed");
  646. let devtoolsToolboxDestroyed = toolbox.destroy();
  647. return promise.all([
  648. debuggerRootActorDisconnected,
  649. debuggerPanelDestroyed,
  650. devtoolsToolboxDestroyed
  651. ]).then(() => aFlags.noTabRemoval ? null : removeTab(tab));
  652. }
  653. function closeDebuggerAndFinish(aPanel, aFlags = {}) {
  654. let thread = aPanel.panelWin.gThreadClient;
  655. if (thread.state == "paused" && !aFlags.whilePaused) {
  656. ok(false, "You should use 'resumeDebuggerThenCloseAndFinish' instead, " +
  657. "unless you're absolutely sure about what you're doing.");
  658. }
  659. return teardown(aPanel, aFlags).then(finish);
  660. }
  661. function resumeDebuggerThenCloseAndFinish(aPanel, aFlags = {}) {
  662. let deferred = promise.defer();
  663. let thread = aPanel.panelWin.gThreadClient;
  664. thread.resume(() => closeDebuggerAndFinish(aPanel, aFlags).then(deferred.resolve));
  665. return deferred.promise;
  666. }
  667. // Blackboxing helpers
  668. function getBlackBoxButton(aPanel) {
  669. return aPanel.panelWin.document.getElementById("black-box");
  670. }
  671. /**
  672. * Returns the node that has the black-boxed class applied to it.
  673. */
  674. function getSelectedSourceElement(aPanel) {
  675. return aPanel.panelWin.DebuggerView.Sources.selectedItem.prebuiltNode;
  676. }
  677. function toggleBlackBoxing(aPanel, aSourceActor = null) {
  678. function clickBlackBoxButton() {
  679. getBlackBoxButton(aPanel).click();
  680. }
  681. const blackBoxChanged = waitForDispatch(
  682. aPanel,
  683. aPanel.panelWin.constants.BLACKBOX
  684. ).then(() => {
  685. return aSourceActor ?
  686. getSource(aPanel, aSourceActor) :
  687. getSelectedSource(aPanel);
  688. });
  689. if (aSourceActor) {
  690. aPanel.panelWin.DebuggerView.Sources.selectedValue = aSourceActor;
  691. ensureSourceIs(aPanel, aSourceActor, true).then(clickBlackBoxButton);
  692. } else {
  693. clickBlackBoxButton();
  694. }
  695. return blackBoxChanged;
  696. }
  697. function selectSourceAndGetBlackBoxButton(aPanel, aUrl) {
  698. function returnBlackboxButton() {
  699. return getBlackBoxButton(aPanel);
  700. }
  701. let sources = aPanel.panelWin.DebuggerView.Sources;
  702. sources.selectedValue = getSourceActor(sources, aUrl);
  703. return ensureSourceIs(aPanel, aUrl, true).then(returnBlackboxButton);
  704. }
  705. // Variables view inspection popup helpers
  706. function openVarPopup(aPanel, aCoords, aWaitForFetchedProperties) {
  707. let events = aPanel.panelWin.EVENTS;
  708. let editor = aPanel.panelWin.DebuggerView.editor;
  709. let bubble = aPanel.panelWin.DebuggerView.VariableBubble;
  710. let tooltip = bubble._tooltip.panel;
  711. let popupShown = once(tooltip, "popupshown");
  712. let fetchedProperties = aWaitForFetchedProperties
  713. ? waitForDebuggerEvents(aPanel, events.FETCHED_BUBBLE_PROPERTIES)
  714. : promise.resolve(null);
  715. let updatedFrame = waitForDebuggerEvents(aPanel, events.FETCHED_SCOPES);
  716. let { left, top } = editor.getCoordsFromPosition(aCoords);
  717. bubble._findIdentifier(left, top);
  718. return promise.all([popupShown, fetchedProperties, updatedFrame]).then(waitForTick);
  719. }
  720. // Simulates the mouse hovering a variable in the debugger
  721. // Takes in account the position of the cursor in the text, if the text is
  722. // selected and if a button is currently pushed (aButtonPushed > 0).
  723. // The function returns a promise which returns true if the popup opened or
  724. // false if it didn't
  725. function intendOpenVarPopup(aPanel, aPosition, aButtonPushed) {
  726. let bubble = aPanel.panelWin.DebuggerView.VariableBubble;
  727. let editor = aPanel.panelWin.DebuggerView.editor;
  728. let tooltip = bubble._tooltip;
  729. let { left, top } = editor.getCoordsFromPosition(aPosition);
  730. const eventDescriptor = {
  731. clientX: left,
  732. clientY: top,
  733. buttons: aButtonPushed
  734. };
  735. bubble._onMouseMove(eventDescriptor);
  736. const deferred = promise.defer();
  737. window.setTimeout(
  738. function () {
  739. if (tooltip.isEmpty()) {
  740. deferred.resolve(false);
  741. } else {
  742. deferred.resolve(true);
  743. }
  744. },
  745. bubble.TOOLTIP_SHOW_DELAY + 1000
  746. );
  747. return deferred.promise;
  748. }
  749. function hideVarPopup(aPanel) {
  750. let bubble = aPanel.panelWin.DebuggerView.VariableBubble;
  751. let tooltip = bubble._tooltip.panel;
  752. let popupHiding = once(tooltip, "popuphiding");
  753. bubble.hideContents();
  754. return popupHiding.then(waitForTick);
  755. }
  756. function hideVarPopupByScrollingEditor(aPanel) {
  757. let editor = aPanel.panelWin.DebuggerView.editor;
  758. let bubble = aPanel.panelWin.DebuggerView.VariableBubble;
  759. let tooltip = bubble._tooltip.panel;
  760. let popupHiding = once(tooltip, "popuphiding");
  761. editor.setFirstVisibleLine(0);
  762. return popupHiding.then(waitForTick);
  763. }
  764. function reopenVarPopup(...aArgs) {
  765. return hideVarPopup.apply(this, aArgs).then(() => openVarPopup.apply(this, aArgs));
  766. }
  767. function attachAddonActorForId(aClient, aAddonId) {
  768. let deferred = promise.defer();
  769. getAddonActorForId(aClient, aAddonId).then(aGrip => {
  770. aClient.attachAddon(aGrip.actor, aResponse => {
  771. deferred.resolve([aGrip, aResponse]);
  772. });
  773. });
  774. return deferred.promise;
  775. }
  776. function doResume(aPanel) {
  777. const threadClient = aPanel.panelWin.gThreadClient;
  778. return threadClient.resume();
  779. }
  780. function doInterrupt(aPanel) {
  781. const threadClient = aPanel.panelWin.gThreadClient;
  782. return threadClient.interrupt();
  783. }
  784. function pushPrefs(...aPrefs) {
  785. let deferred = promise.defer();
  786. SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve);
  787. return deferred.promise;
  788. }
  789. function popPrefs() {
  790. let deferred = promise.defer();
  791. SpecialPowers.popPrefEnv(deferred.resolve);
  792. return deferred.promise;
  793. }
  794. // Source helpers
  795. function getSelectedSource(panel) {
  796. const win = panel.panelWin;
  797. return win.queries.getSelectedSource(win.DebuggerController.getState());
  798. }
  799. function getSource(panel, actor) {
  800. const win = panel.panelWin;
  801. return win.queries.getSource(win.DebuggerController.getState(), actor);
  802. }
  803. function getSelectedSourceURL(aSources) {
  804. return (aSources.selectedItem &&
  805. aSources.selectedItem.attachment.source.url);
  806. }
  807. function getSourceURL(aSources, aActor) {
  808. let item = aSources.getItemByValue(aActor);
  809. return item && item.attachment.source.url;
  810. }
  811. function getSourceActor(aSources, aURL) {
  812. let item = aSources.getItemForAttachment(a => a.source && a.source.url === aURL);
  813. return item && item.value;
  814. }
  815. function getSourceForm(aSources, aURL) {
  816. let item = aSources.getItemByValue(getSourceActor(aSources, aURL));
  817. return item.attachment.source;
  818. }
  819. var nextId = 0;
  820. function jsonrpc(tab, method, params) {
  821. return new Promise(function (resolve, reject) {
  822. let currentId = nextId++;
  823. let messageManager = tab.linkedBrowser.messageManager;
  824. messageManager.sendAsyncMessage("jsonrpc", {
  825. method: method,
  826. params: params,
  827. id: currentId
  828. });
  829. messageManager.addMessageListener("jsonrpc", function listener(res) {
  830. const { data: { result, error, id } } = res;
  831. if (id !== currentId) {
  832. return;
  833. }
  834. messageManager.removeMessageListener("jsonrpc", listener);
  835. if (error != null) {
  836. reject(error);
  837. }
  838. resolve(result);
  839. });
  840. });
  841. }
  842. function callInTab(tab, name) {
  843. info("Calling function with name '" + name + "' in tab.");
  844. return jsonrpc(tab, "call", [name, Array.prototype.slice.call(arguments, 2)]);
  845. }
  846. function evalInTab(tab, string) {
  847. info("Evalling string in tab.");
  848. return jsonrpc(tab, "_eval", [string]);
  849. }
  850. function createWorkerInTab(tab, url) {
  851. info("Creating worker with url '" + url + "' in tab.");
  852. return jsonrpc(tab, "createWorker", [url]);
  853. }
  854. function terminateWorkerInTab(tab, url) {
  855. info("Terminating worker with url '" + url + "' in tab.");
  856. return jsonrpc(tab, "terminateWorker", [url]);
  857. }
  858. function postMessageToWorkerInTab(tab, url, message) {
  859. info("Posting message to worker with url '" + url + "' in tab.");
  860. return jsonrpc(tab, "postMessageToWorker", [url, message]);
  861. }
  862. function generateMouseClickInTab(tab, path) {
  863. info("Generating mouse click in tab.");
  864. return jsonrpc(tab, "generateMouseClick", [path]);
  865. }
  866. function connect(client) {
  867. info("Connecting client.");
  868. return client.connect();
  869. }
  870. function close(client) {
  871. info("Waiting for client to close.\n");
  872. return client.close();
  873. }
  874. function listTabs(client) {
  875. info("Listing tabs.");
  876. return client.listTabs();
  877. }
  878. function findTab(tabs, url) {
  879. info("Finding tab with url '" + url + "'.");
  880. for (let tab of tabs) {
  881. if (tab.url === url) {
  882. return tab;
  883. }
  884. }
  885. return null;
  886. }
  887. function attachTab(client, tab) {
  888. info("Attaching to tab with url '" + tab.url + "'.");
  889. return new Promise(function (resolve) {
  890. client.attachTab(tab.actor, function (response, tabClient) {
  891. resolve([response, tabClient]);
  892. });
  893. });
  894. }
  895. function listWorkers(tabClient) {
  896. info("Listing workers.");
  897. return new Promise(function (resolve) {
  898. tabClient.listWorkers(function (response) {
  899. resolve(response);
  900. });
  901. });
  902. }
  903. function findWorker(workers, url) {
  904. info("Finding worker with url '" + url + "'.");
  905. for (let worker of workers) {
  906. if (worker.url === url) {
  907. return worker;
  908. }
  909. }
  910. return null;
  911. }
  912. function attachWorker(tabClient, worker) {
  913. info("Attaching to worker with url '" + worker.url + "'.");
  914. return new Promise(function (resolve, reject) {
  915. tabClient.attachWorker(worker.actor, function (response, workerClient) {
  916. resolve([response, workerClient]);
  917. });
  918. });
  919. }
  920. function waitForWorkerListChanged(tabClient) {
  921. info("Waiting for worker list to change.");
  922. return new Promise(function (resolve) {
  923. tabClient.addListener("workerListChanged", function listener() {
  924. tabClient.removeListener("workerListChanged", listener);
  925. resolve();
  926. });
  927. });
  928. }
  929. function attachThread(workerClient, options) {
  930. info("Attaching to thread.");
  931. return new Promise(function (resolve, reject) {
  932. workerClient.attachThread(options, function (response, threadClient) {
  933. resolve([response, threadClient]);
  934. });
  935. });
  936. }
  937. function waitForWorkerClose(workerClient) {
  938. info("Waiting for worker to close.");
  939. return new Promise(function (resolve) {
  940. workerClient.addOneTimeListener("close", function () {
  941. info("Worker did close.");
  942. resolve();
  943. });
  944. });
  945. }
  946. function resume(threadClient) {
  947. info("Resuming thread.");
  948. return threadClient.resume();
  949. }
  950. function findSource(sources, url) {
  951. info("Finding source with url '" + url + "'.\n");
  952. for (let source of sources) {
  953. if (source.url === url) {
  954. return source;
  955. }
  956. }
  957. return null;
  958. }
  959. function waitForEvent(client, type, predicate) {
  960. return new Promise(function (resolve) {
  961. function listener(type, packet) {
  962. if (!predicate(packet)) {
  963. return;
  964. }
  965. client.removeListener(listener);
  966. resolve(packet);
  967. }
  968. if (predicate) {
  969. client.addListener(type, listener);
  970. } else {
  971. client.addOneTimeListener(type, function (type, packet) {
  972. resolve(packet);
  973. });
  974. }
  975. });
  976. }
  977. function waitForPause(threadClient) {
  978. info("Waiting for pause.\n");
  979. return waitForEvent(threadClient, "paused");
  980. }
  981. function setBreakpoint(sourceClient, location) {
  982. info("Setting breakpoint.\n");
  983. return sourceClient.setBreakpoint(location);
  984. }
  985. function source(sourceClient) {
  986. info("Getting source.\n");
  987. return sourceClient.source();
  988. }
  989. // Return a promise with a reference to jsterm, opening the split
  990. // console if necessary. This cleans up the split console pref so
  991. // it won't pollute other tests.
  992. function getSplitConsole(toolbox, win) {
  993. registerCleanupFunction(() => {
  994. Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
  995. });
  996. if (!win) {
  997. win = toolbox.win;
  998. }
  999. if (!toolbox.splitConsole) {
  1000. EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
  1001. }
  1002. return new Promise(resolve => {
  1003. toolbox.getPanelWhenReady("webconsole").then(() => {
  1004. ok(toolbox.splitConsole, "Split console is shown.");
  1005. let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
  1006. resolve(jsterm);
  1007. });
  1008. });
  1009. }
  1010. // navigation
  1011. function waitForNavigation(gPanel) {
  1012. const target = gPanel.panelWin.gTarget;
  1013. const deferred = promise.defer();
  1014. target.once("navigate", () => {
  1015. deferred.resolve();
  1016. });
  1017. info("Waiting for navigation...");
  1018. return deferred.promise;
  1019. }
  1020. // actions
  1021. function bindActionCreators(panel) {
  1022. const win = panel.panelWin;
  1023. const dispatch = win.DebuggerController.dispatch;
  1024. const { bindActionCreators } = win.require("devtools/client/shared/vendor/redux");
  1025. return bindActionCreators(win.actions, dispatch);
  1026. }
  1027. // Wait until an action of `type` is dispatched. This is different
  1028. // then `_afterDispatchDone` because it doesn't wait for async actions
  1029. // to be done/errored. Use this if you want to listen for the "start"
  1030. // action of an async operation (somewhat rare).
  1031. function waitForNextDispatch(store, type) {
  1032. return new Promise(resolve => {
  1033. store.dispatch({
  1034. // Normally we would use `services.WAIT_UNTIL`, but use the
  1035. // internal name here so tests aren't forced to always pass it
  1036. // in
  1037. type: "@@service/waitUntil",
  1038. predicate: action => action.type === type,
  1039. run: (dispatch, getState, action) => {
  1040. resolve(action);
  1041. }
  1042. });
  1043. });
  1044. }
  1045. // Wait until an action of `type` is dispatched. If it's part of an
  1046. // async operation, wait until the `status` field is "done" or "error"
  1047. function _afterDispatchDone(store, type) {
  1048. return new Promise(resolve => {
  1049. store.dispatch({
  1050. // Normally we would use `services.WAIT_UNTIL`, but use the
  1051. // internal name here so tests aren't forced to always pass it
  1052. // in
  1053. type: "@@service/waitUntil",
  1054. predicate: action => {
  1055. if (action.type === type) {
  1056. return action.status ?
  1057. (action.status === "done" || action.status === "error") :
  1058. true;
  1059. }
  1060. },
  1061. run: (dispatch, getState, action) => {
  1062. resolve(action);
  1063. }
  1064. });
  1065. });
  1066. }
  1067. function waitForDispatch(panel, type, eventRepeat = 1) {
  1068. const controller = panel.panelWin.DebuggerController;
  1069. const actionType = panel.panelWin.constants[type];
  1070. let count = 0;
  1071. return Task.spawn(function* () {
  1072. info("Waiting for " + type + " to dispatch " + eventRepeat + " time(s)");
  1073. while (count < eventRepeat) {
  1074. yield _afterDispatchDone(controller, actionType);
  1075. count++;
  1076. info(type + " dispatched " + count + " time(s)");
  1077. }
  1078. });
  1079. }
  1080. function* initWorkerDebugger(TAB_URL, WORKER_URL) {
  1081. if (!DebuggerServer.initialized) {
  1082. DebuggerServer.init();
  1083. DebuggerServer.addBrowserActors();
  1084. }
  1085. let client = new DebuggerClient(DebuggerServer.connectPipe());
  1086. yield connect(client);
  1087. let tab = yield addTab(TAB_URL);
  1088. let { tabs } = yield listTabs(client);
  1089. let [, tabClient] = yield attachTab(client, findTab(tabs, TAB_URL));
  1090. yield createWorkerInTab(tab, WORKER_URL);
  1091. let { workers } = yield listWorkers(tabClient);
  1092. let [, workerClient] = yield attachWorker(tabClient,
  1093. findWorker(workers, WORKER_URL));
  1094. let toolbox = yield gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
  1095. "jsdebugger",
  1096. Toolbox.HostType.WINDOW);
  1097. let debuggerPanel = toolbox.getCurrentPanel();
  1098. let gDebugger = debuggerPanel.panelWin;
  1099. return {client, tab, tabClient, workerClient, toolbox, gDebugger};
  1100. }