michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: "use strict"; michael@0: michael@0: const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; michael@0: michael@0: let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); michael@0: michael@0: // Disable logging for faster test runs. Set this pref to true if you want to michael@0: // debug a test in your try runs. Both the debugger server and frontend will michael@0: // be affected by this pref. michael@0: let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log"); michael@0: Services.prefs.setBoolPref("devtools.debugger.log", false); michael@0: michael@0: let { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); michael@0: let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); michael@0: let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); michael@0: let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); michael@0: let { require } = devtools; michael@0: let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {}); michael@0: let { BrowserToolboxProcess } = Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {}); michael@0: let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {}); michael@0: let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {}); michael@0: let { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {}); michael@0: const { promiseInvoke } = require("devtools/async-utils"); michael@0: let TargetFactory = devtools.TargetFactory; michael@0: let Toolbox = devtools.Toolbox; michael@0: michael@0: const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/"; michael@0: michael@0: gDevTools.testing = true; michael@0: SimpleTest.registerCleanupFunction(() => { michael@0: gDevTools.testing = false; michael@0: }); michael@0: michael@0: // All tests are asynchronous. michael@0: waitForExplicitFinish(); michael@0: michael@0: registerCleanupFunction(function() { michael@0: info("finish() was called, cleaning up..."); michael@0: Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging); michael@0: michael@0: // Properly shut down the server to avoid memory leaks. michael@0: DebuggerServer.destroy(); michael@0: michael@0: // Debugger tests use a lot of memory, so force a GC to help fragmentation. michael@0: info("Forcing GC after debugger test."); michael@0: Cu.forceGC(); michael@0: }); michael@0: michael@0: // Import the GCLI test helper michael@0: let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); michael@0: Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this); michael@0: michael@0: // Redeclare dbg_assert with a fatal behavior. michael@0: function dbg_assert(cond, e) { michael@0: if (!cond) { michael@0: throw e; michael@0: } michael@0: } michael@0: michael@0: function addWindow(aUrl) { michael@0: info("Adding window: " + aUrl); michael@0: return promise.resolve(getDOMWindow(window.open(aUrl))); michael@0: } michael@0: michael@0: function getDOMWindow(aReference) { michael@0: return aReference michael@0: .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem michael@0: .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); michael@0: } michael@0: michael@0: function addTab(aUrl, aWindow) { michael@0: info("Adding tab: " + aUrl); michael@0: michael@0: let deferred = promise.defer(); michael@0: let targetWindow = aWindow || window; michael@0: let targetBrowser = targetWindow.gBrowser; michael@0: michael@0: targetWindow.focus(); michael@0: let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl); michael@0: let linkedBrowser = tab.linkedBrowser; michael@0: michael@0: linkedBrowser.addEventListener("load", function onLoad() { michael@0: linkedBrowser.removeEventListener("load", onLoad, true); michael@0: info("Tab added and finished loading: " + aUrl); michael@0: deferred.resolve(tab); michael@0: }, true); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function removeTab(aTab, aWindow) { michael@0: info("Removing tab."); michael@0: michael@0: let deferred = promise.defer(); michael@0: let targetWindow = aWindow || window; michael@0: let targetBrowser = targetWindow.gBrowser; michael@0: let tabContainer = targetBrowser.tabContainer; michael@0: michael@0: tabContainer.addEventListener("TabClose", function onClose(aEvent) { michael@0: tabContainer.removeEventListener("TabClose", onClose, false); michael@0: info("Tab removed and finished closing."); michael@0: deferred.resolve(); michael@0: }, false); michael@0: michael@0: targetBrowser.removeTab(aTab); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function addAddon(aUrl) { michael@0: info("Installing addon: " + aUrl); michael@0: michael@0: let deferred = promise.defer(); michael@0: michael@0: AddonManager.getInstallForURL(aUrl, aInstaller => { michael@0: aInstaller.install(); michael@0: let listener = { michael@0: onInstallEnded: function(aAddon, aAddonInstall) { michael@0: aInstaller.removeListener(listener); michael@0: michael@0: // Wait for add-on's startup scripts to execute. See bug 997408 michael@0: executeSoon(function() { michael@0: deferred.resolve(aAddonInstall); michael@0: }); michael@0: } michael@0: }; michael@0: aInstaller.addListener(listener); michael@0: }, "application/x-xpinstall"); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function removeAddon(aAddon) { michael@0: info("Removing addon."); michael@0: michael@0: let deferred = promise.defer(); michael@0: michael@0: let listener = { michael@0: onUninstalled: function(aUninstalledAddon) { michael@0: if (aUninstalledAddon != aAddon) { michael@0: return; michael@0: } michael@0: AddonManager.removeAddonListener(listener); michael@0: deferred.resolve(); michael@0: } michael@0: }; michael@0: AddonManager.addAddonListener(listener); michael@0: aAddon.uninstall(); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function getTabActorForUrl(aClient, aUrl) { michael@0: let deferred = promise.defer(); michael@0: michael@0: aClient.listTabs(aResponse => { michael@0: let tabActor = aResponse.tabs.filter(aGrip => aGrip.url == aUrl).pop(); michael@0: deferred.resolve(tabActor); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function getAddonActorForUrl(aClient, aUrl) { michael@0: info("Get addon actor for URL: " + aUrl); michael@0: let deferred = promise.defer(); michael@0: michael@0: aClient.listAddons(aResponse => { michael@0: let addonActor = aResponse.addons.filter(aGrip => aGrip.url == aUrl).pop(); michael@0: info("got addon actor for URL: " + addonActor.actor); michael@0: deferred.resolve(addonActor); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function attachTabActorForUrl(aClient, aUrl) { michael@0: let deferred = promise.defer(); michael@0: michael@0: getTabActorForUrl(aClient, aUrl).then(aGrip => { michael@0: aClient.attachTab(aGrip.actor, aResponse => { michael@0: deferred.resolve([aGrip, aResponse]); michael@0: }); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function attachThreadActorForUrl(aClient, aUrl) { michael@0: let deferred = promise.defer(); michael@0: michael@0: attachTabActorForUrl(aClient, aUrl).then(([aGrip, aResponse]) => { michael@0: aClient.attachThread(aResponse.threadActor, (aResponse, aThreadClient) => { michael@0: aThreadClient.resume(aResponse => { michael@0: deferred.resolve(aThreadClient); michael@0: }); michael@0: }); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function once(aTarget, aEventName, aUseCapture = false) { michael@0: info("Waiting for event: '" + aEventName + "' on " + aTarget + "."); michael@0: michael@0: let deferred = promise.defer(); michael@0: michael@0: for (let [add, remove] of [ michael@0: ["addEventListener", "removeEventListener"], michael@0: ["addListener", "removeListener"], michael@0: ["on", "off"] michael@0: ]) { michael@0: if ((add in aTarget) && (remove in aTarget)) { michael@0: aTarget[add](aEventName, function onEvent(...aArgs) { michael@0: aTarget[remove](aEventName, onEvent, aUseCapture); michael@0: deferred.resolve.apply(deferred, aArgs); michael@0: }, aUseCapture); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function waitForTick() { michael@0: let deferred = promise.defer(); michael@0: executeSoon(deferred.resolve); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function waitForTime(aDelay) { michael@0: let deferred = promise.defer(); michael@0: setTimeout(deferred.resolve, aDelay); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function waitForSourceShown(aPanel, aUrl) { michael@0: return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.SOURCE_SHOWN).then(aSource => { michael@0: let sourceUrl = aSource.url; michael@0: info("Source shown: " + sourceUrl); michael@0: michael@0: if (!sourceUrl.contains(aUrl)) { michael@0: return waitForSourceShown(aPanel, aUrl); michael@0: } else { michael@0: ok(true, "The correct source has been shown."); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: function waitForEditorLocationSet(aPanel) { michael@0: return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.EDITOR_LOCATION_SET); michael@0: } michael@0: michael@0: function ensureSourceIs(aPanel, aUrl, aWaitFlag = false) { michael@0: if (aPanel.panelWin.DebuggerView.Sources.selectedValue.contains(aUrl)) { michael@0: ok(true, "Expected source is shown: " + aUrl); michael@0: return promise.resolve(null); michael@0: } michael@0: if (aWaitFlag) { michael@0: return waitForSourceShown(aPanel, aUrl); michael@0: } michael@0: ok(false, "Expected source was not already shown: " + aUrl); michael@0: return promise.reject(null); michael@0: } michael@0: michael@0: function waitForCaretUpdated(aPanel, aLine, aCol = 1) { michael@0: return waitForEditorEvents(aPanel, "cursorActivity").then(() => { michael@0: let cursor = aPanel.panelWin.DebuggerView.editor.getCursor(); michael@0: info("Caret updated: " + (cursor.line + 1) + ", " + (cursor.ch + 1)); michael@0: michael@0: if (!isCaretPos(aPanel, aLine, aCol)) { michael@0: return waitForCaretUpdated(aPanel, aLine, aCol); michael@0: } else { michael@0: ok(true, "The correct caret position has been set."); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: function ensureCaretAt(aPanel, aLine, aCol = 1, aWaitFlag = false) { michael@0: if (isCaretPos(aPanel, aLine, aCol)) { michael@0: ok(true, "Expected caret position is set: " + aLine + "," + aCol); michael@0: return promise.resolve(null); michael@0: } michael@0: if (aWaitFlag) { michael@0: return waitForCaretUpdated(aPanel, aLine, aCol); michael@0: } michael@0: ok(false, "Expected caret position was not already set: " + aLine + "," + aCol); michael@0: return promise.reject(null); michael@0: } michael@0: michael@0: function isCaretPos(aPanel, aLine, aCol = 1) { michael@0: let editor = aPanel.panelWin.DebuggerView.editor; michael@0: let cursor = editor.getCursor(); michael@0: michael@0: // Source editor starts counting line and column numbers from 0. michael@0: info("Current editor caret position: " + (cursor.line + 1) + ", " + (cursor.ch + 1)); michael@0: return cursor.line == (aLine - 1) && cursor.ch == (aCol - 1); michael@0: } michael@0: michael@0: function isEditorSel(aPanel, [start, end]) { michael@0: let editor = aPanel.panelWin.DebuggerView.editor; michael@0: let range = { michael@0: start: editor.getOffset(editor.getCursor("start")), michael@0: end: editor.getOffset(editor.getCursor()) michael@0: }; michael@0: michael@0: // Source editor starts counting line and column numbers from 0. michael@0: info("Current editor selection: " + (range.start + 1) + ", " + (range.end + 1)); michael@0: return range.start == (start - 1) && range.end == (end - 1); michael@0: } michael@0: michael@0: function waitForSourceAndCaret(aPanel, aUrl, aLine, aCol) { michael@0: return promise.all([ michael@0: waitForSourceShown(aPanel, aUrl), michael@0: waitForCaretUpdated(aPanel, aLine, aCol) michael@0: ]); michael@0: } michael@0: michael@0: function waitForCaretAndScopes(aPanel, aLine, aCol) { michael@0: return promise.all([ michael@0: waitForCaretUpdated(aPanel, aLine, aCol), michael@0: waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.FETCHED_SCOPES) michael@0: ]); michael@0: } michael@0: michael@0: function waitForSourceAndCaretAndScopes(aPanel, aUrl, aLine, aCol) { michael@0: return promise.all([ michael@0: waitForSourceAndCaret(aPanel, aUrl, aLine, aCol), michael@0: waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.FETCHED_SCOPES) michael@0: ]); michael@0: } michael@0: michael@0: function waitForDebuggerEvents(aPanel, aEventName, aEventRepeat = 1) { michael@0: info("Waiting for debugger event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s)."); michael@0: michael@0: let deferred = promise.defer(); michael@0: let panelWin = aPanel.panelWin; michael@0: let count = 0; michael@0: michael@0: panelWin.on(aEventName, function onEvent(aEventName, ...aArgs) { michael@0: info("Debugger event '" + aEventName + "' fired: " + (++count) + " time(s)."); michael@0: michael@0: if (count == aEventRepeat) { michael@0: ok(true, "Enough '" + aEventName + "' panel events have been fired."); michael@0: panelWin.off(aEventName, onEvent); michael@0: deferred.resolve.apply(deferred, aArgs); michael@0: } michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function waitForEditorEvents(aPanel, aEventName, aEventRepeat = 1) { michael@0: info("Waiting for editor event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s)."); michael@0: michael@0: let deferred = promise.defer(); michael@0: let editor = aPanel.panelWin.DebuggerView.editor; michael@0: let count = 0; michael@0: michael@0: editor.on(aEventName, function onEvent(...aArgs) { michael@0: info("Editor event '" + aEventName + "' fired: " + (++count) + " time(s)."); michael@0: michael@0: if (count == aEventRepeat) { michael@0: ok(true, "Enough '" + aEventName + "' editor events have been fired."); michael@0: editor.off(aEventName, onEvent); michael@0: deferred.resolve.apply(deferred, aArgs); michael@0: } michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function waitForThreadEvents(aPanel, aEventName, aEventRepeat = 1) { michael@0: info("Waiting for thread event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s)."); michael@0: michael@0: let deferred = promise.defer(); michael@0: let thread = aPanel.panelWin.gThreadClient; michael@0: let count = 0; michael@0: michael@0: thread.addListener(aEventName, function onEvent(aEventName, ...aArgs) { michael@0: info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s)."); michael@0: michael@0: if (count == aEventRepeat) { michael@0: ok(true, "Enough '" + aEventName + "' thread events have been fired."); michael@0: thread.removeListener(aEventName, onEvent); michael@0: deferred.resolve.apply(deferred, aArgs); michael@0: } michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function waitForClientEvents(aPanel, aEventName, aEventRepeat = 1) { michael@0: info("Waiting for client event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s)."); michael@0: michael@0: let deferred = promise.defer(); michael@0: let client = aPanel.panelWin.gClient; michael@0: let count = 0; michael@0: michael@0: client.addListener(aEventName, function onEvent(aEventName, ...aArgs) { michael@0: info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s)."); michael@0: michael@0: if (count == aEventRepeat) { michael@0: ok(true, "Enough '" + aEventName + "' thread events have been fired."); michael@0: client.removeListener(aEventName, onEvent); michael@0: deferred.resolve.apply(deferred, aArgs); michael@0: } michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function ensureThreadClientState(aPanel, aState) { michael@0: let thread = aPanel.panelWin.gThreadClient; michael@0: let state = thread.state; michael@0: michael@0: info("Thread is: '" + state + "'."); michael@0: michael@0: if (state == aState) { michael@0: return promise.resolve(null); michael@0: } else { michael@0: return waitForThreadEvents(aPanel, aState); michael@0: } michael@0: } michael@0: michael@0: function navigateActiveTabTo(aPanel, aUrl, aWaitForEventName, aEventRepeat) { michael@0: let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat); michael@0: let activeTab = aPanel.panelWin.DebuggerController._target.activeTab; michael@0: aUrl ? activeTab.navigateTo(aUrl) : activeTab.reload(); michael@0: return finished; michael@0: } michael@0: michael@0: function navigateActiveTabInHistory(aPanel, aDirection, aWaitForEventName, aEventRepeat) { michael@0: let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat); michael@0: content.history[aDirection](); michael@0: return finished; michael@0: } michael@0: michael@0: function reloadActiveTab(aPanel, aWaitForEventName, aEventRepeat) { michael@0: return navigateActiveTabTo(aPanel, null, aWaitForEventName, aEventRepeat); michael@0: } michael@0: michael@0: function clearText(aElement) { michael@0: info("Clearing text..."); michael@0: aElement.focus(); michael@0: aElement.value = ""; michael@0: } michael@0: michael@0: function setText(aElement, aText) { michael@0: clearText(aElement); michael@0: info("Setting text: " + aText); michael@0: aElement.value = aText; michael@0: } michael@0: michael@0: function typeText(aElement, aText) { michael@0: info("Typing text: " + aText); michael@0: aElement.focus(); michael@0: EventUtils.sendString(aText, aElement.ownerDocument.defaultView); michael@0: } michael@0: michael@0: function backspaceText(aElement, aTimes) { michael@0: info("Pressing backspace " + aTimes + " times."); michael@0: for (let i = 0; i < aTimes; i++) { michael@0: aElement.focus(); michael@0: EventUtils.sendKey("BACK_SPACE", aElement.ownerDocument.defaultView); michael@0: } michael@0: } michael@0: michael@0: function getTab(aTarget, aWindow) { michael@0: if (aTarget instanceof XULElement) { michael@0: return promise.resolve(aTarget); michael@0: } else { michael@0: return addTab(aTarget, aWindow); michael@0: } michael@0: } michael@0: michael@0: function getSources(aClient) { michael@0: let deferred = promise.defer(); michael@0: michael@0: aClient.getSources(({sources}) => deferred.resolve(sources)); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function initDebugger(aTarget, aWindow) { michael@0: info("Initializing a debugger panel."); michael@0: michael@0: return getTab(aTarget, aWindow).then(aTab => { michael@0: info("Debugee tab added successfully: " + aTarget); michael@0: michael@0: let deferred = promise.defer(); michael@0: let debuggee = aTab.linkedBrowser.contentWindow.wrappedJSObject; michael@0: let target = TargetFactory.forTab(aTab); michael@0: michael@0: gDevTools.showToolbox(target, "jsdebugger").then(aToolbox => { michael@0: info("Debugger panel shown successfully."); michael@0: michael@0: let debuggerPanel = aToolbox.getCurrentPanel(); michael@0: let panelWin = debuggerPanel.panelWin; michael@0: michael@0: // Wait for the initial resume... michael@0: panelWin.gClient.addOneTimeListener("resumed", () => { michael@0: info("Debugger client resumed successfully."); michael@0: michael@0: prepareDebugger(debuggerPanel); michael@0: deferred.resolve([aTab, debuggee, debuggerPanel, aWindow]); michael@0: }); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: }); michael@0: } michael@0: michael@0: // Creates an add-on debugger for a given add-on. The returned AddonDebugger michael@0: // object must be destroyed before finishing the test michael@0: function initAddonDebugger(aUrl) { michael@0: let addonDebugger = new AddonDebugger(); michael@0: return addonDebugger.init(aUrl).then(() => addonDebugger); michael@0: } michael@0: michael@0: function AddonDebugger() { michael@0: this._onMessage = this._onMessage.bind(this); michael@0: } michael@0: michael@0: AddonDebugger.prototype = { michael@0: init: Task.async(function*(aUrl) { michael@0: info("Initializing an addon debugger panel."); michael@0: michael@0: if (!DebuggerServer.initialized) { michael@0: DebuggerServer.init(() => true); michael@0: DebuggerServer.addBrowserActors(); michael@0: } michael@0: michael@0: this.frame = document.createElement("iframe"); michael@0: this.frame.setAttribute("height", 400); michael@0: document.documentElement.appendChild(this.frame); michael@0: window.addEventListener("message", this._onMessage); michael@0: michael@0: let transport = DebuggerServer.connectPipe(); michael@0: this.client = new DebuggerClient(transport); michael@0: michael@0: let connected = promise.defer(); michael@0: this.client.connect(connected.resolve); michael@0: yield connected.promise; michael@0: michael@0: let addonActor = yield getAddonActorForUrl(this.client, aUrl); michael@0: michael@0: let targetOptions = { michael@0: form: { addonActor: addonActor.actor, title: addonActor.name }, michael@0: client: this.client, michael@0: chrome: true michael@0: }; michael@0: michael@0: let toolboxOptions = { michael@0: customIframe: this.frame michael@0: }; michael@0: michael@0: let target = devtools.TargetFactory.forTab(targetOptions); michael@0: let toolbox = yield gDevTools.showToolbox(target, "jsdebugger", devtools.Toolbox.HostType.CUSTOM, toolboxOptions); michael@0: michael@0: info("Addon debugger panel shown successfully."); michael@0: michael@0: this.debuggerPanel = toolbox.getCurrentPanel(); michael@0: michael@0: // Wait for the initial resume... michael@0: yield waitForClientEvents(this.debuggerPanel, "resumed"); michael@0: yield prepareDebugger(this.debuggerPanel); michael@0: }), michael@0: michael@0: destroy: Task.async(function*() { michael@0: let deferred = promise.defer(); michael@0: this.client.close(deferred.resolve); michael@0: yield deferred.promise; michael@0: yield this.debuggerPanel._toolbox.destroy(); michael@0: this.frame.remove(); michael@0: window.removeEventListener("message", this._onMessage); michael@0: }), michael@0: michael@0: /** michael@0: * Returns a list of the groups and sources in the UI. The returned array michael@0: * contains objects for each group with properties name and sources. The michael@0: * sources property contains an array with objects for each source for that michael@0: * group with properties label and url. michael@0: */ michael@0: getSourceGroups: Task.async(function*() { michael@0: let debuggerWin = this.debuggerPanel.panelWin; michael@0: let sources = yield getSources(debuggerWin.gThreadClient); michael@0: ok(sources.length, "retrieved sources"); michael@0: michael@0: // groups will be the return value, groupmap and the maps we put in it will michael@0: // be used as quick lookups to add the url information in below michael@0: let groups = []; michael@0: let groupmap = new Map(); michael@0: michael@0: let uigroups = this.debuggerPanel.panelWin.document.querySelectorAll(".side-menu-widget-group"); michael@0: for (let g of uigroups) { michael@0: let name = g.querySelector(".side-menu-widget-group-title .name").value; michael@0: let group = { michael@0: name: name, michael@0: sources: [] michael@0: }; michael@0: groups.push(group); michael@0: let labelmap = new Map(); michael@0: groupmap.set(name, labelmap); michael@0: michael@0: for (let l of g.querySelectorAll(".dbg-source-item")) { michael@0: let source = { michael@0: label: l.value, michael@0: url: null michael@0: }; michael@0: michael@0: labelmap.set(l.value, source); michael@0: group.sources.push(source); michael@0: } michael@0: } michael@0: michael@0: for (let source of sources) { michael@0: let { label, group } = debuggerWin.DebuggerView.Sources.getItemByValue(source.url).attachment; michael@0: michael@0: if (!groupmap.has(group)) { michael@0: ok(false, "Saw a source group not in the UI: " + group); michael@0: continue; michael@0: } michael@0: michael@0: if (!groupmap.get(group).has(label)) { michael@0: ok(false, "Saw a source label not in the UI: " + label); michael@0: continue; michael@0: } michael@0: michael@0: groupmap.get(group).get(label).url = source.url.split(" -> ").pop(); michael@0: } michael@0: michael@0: return groups; michael@0: }), michael@0: michael@0: _onMessage: function(event) { michael@0: let json = JSON.parse(event.data); michael@0: switch (json.name) { michael@0: case "toolbox-title": michael@0: this.title = json.data.value; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: function initChromeDebugger(aOnClose) { michael@0: info("Initializing a chrome debugger process."); michael@0: michael@0: let deferred = promise.defer(); michael@0: michael@0: // Wait for the toolbox process to start... michael@0: BrowserToolboxProcess.init(aOnClose, (aEvent, aProcess) => { michael@0: info("Browser toolbox process started successfully."); michael@0: michael@0: prepareDebugger(aProcess); michael@0: deferred.resolve(aProcess); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function prepareDebugger(aDebugger) { michael@0: if ("target" in aDebugger) { michael@0: let view = aDebugger.panelWin.DebuggerView; michael@0: view.Variables.lazyEmpty = false; michael@0: view.Variables.lazySearch = false; michael@0: view.FilteredSources._autoSelectFirstItem = true; michael@0: view.FilteredFunctions._autoSelectFirstItem = true; michael@0: } else { michael@0: // Nothing to do here yet. michael@0: } michael@0: } michael@0: michael@0: function teardown(aPanel, aFlags = {}) { michael@0: info("Destroying the specified debugger."); michael@0: michael@0: let toolbox = aPanel._toolbox; michael@0: let tab = aPanel.target.tab; michael@0: let debuggerRootActorDisconnected = once(window, "Debugger:Shutdown"); michael@0: let debuggerPanelDestroyed = once(aPanel, "destroyed"); michael@0: let devtoolsToolboxDestroyed = toolbox.destroy(); michael@0: michael@0: return promise.all([ michael@0: debuggerRootActorDisconnected, michael@0: debuggerPanelDestroyed, michael@0: devtoolsToolboxDestroyed michael@0: ]).then(() => aFlags.noTabRemoval ? null : removeTab(tab)); michael@0: } michael@0: michael@0: function closeDebuggerAndFinish(aPanel, aFlags = {}) { michael@0: let thread = aPanel.panelWin.gThreadClient; michael@0: if (thread.state == "paused" && !aFlags.whilePaused) { michael@0: ok(false, "You should use 'resumeDebuggerThenCloseAndFinish' instead, " + michael@0: "unless you're absolutely sure about what you're doing."); michael@0: } michael@0: return teardown(aPanel, aFlags).then(finish); michael@0: } michael@0: michael@0: function resumeDebuggerThenCloseAndFinish(aPanel, aFlags = {}) { michael@0: let deferred = promise.defer(); michael@0: let thread = aPanel.panelWin.gThreadClient; michael@0: thread.resume(() => closeDebuggerAndFinish(aPanel, aFlags).then(deferred.resolve)); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: // Blackboxing helpers michael@0: michael@0: function getBlackBoxButton(aPanel) { michael@0: return aPanel.panelWin.document.getElementById("black-box"); michael@0: } michael@0: michael@0: function toggleBlackBoxing(aPanel, aSource = null) { michael@0: function clickBlackBoxButton() { michael@0: getBlackBoxButton(aPanel).click(); michael@0: } michael@0: michael@0: const blackBoxChanged = waitForThreadEvents(aPanel, "blackboxchange"); michael@0: michael@0: if (aSource) { michael@0: aPanel.panelWin.DebuggerView.Sources.selectedValue = aSource; michael@0: ensureSourceIs(aPanel, aSource, true).then(clickBlackBoxButton); michael@0: } else { michael@0: clickBlackBoxButton(); michael@0: } michael@0: michael@0: return blackBoxChanged; michael@0: } michael@0: michael@0: function selectSourceAndGetBlackBoxButton(aPanel, aSource) { michael@0: function returnBlackboxButton() { michael@0: return getBlackBoxButton(aPanel); michael@0: } michael@0: michael@0: aPanel.panelWin.DebuggerView.Sources.selectedValue = aSource; michael@0: return ensureSourceIs(aPanel, aSource, true).then(returnBlackboxButton); michael@0: } michael@0: michael@0: // Variables view inspection popup helpers michael@0: michael@0: function openVarPopup(aPanel, aCoords, aWaitForFetchedProperties) { michael@0: let events = aPanel.panelWin.EVENTS; michael@0: let editor = aPanel.panelWin.DebuggerView.editor; michael@0: let bubble = aPanel.panelWin.DebuggerView.VariableBubble; michael@0: let tooltip = bubble._tooltip.panel; michael@0: michael@0: let popupShown = once(tooltip, "popupshown"); michael@0: let fetchedProperties = aWaitForFetchedProperties michael@0: ? waitForDebuggerEvents(aPanel, events.FETCHED_BUBBLE_PROPERTIES) michael@0: : promise.resolve(null); michael@0: michael@0: let { left, top } = editor.getCoordsFromPosition(aCoords); michael@0: bubble._findIdentifier(left, top); michael@0: return promise.all([popupShown, fetchedProperties]).then(waitForTick); michael@0: } michael@0: michael@0: // Simulates the mouse hovering a variable in the debugger michael@0: // Takes in account the position of the cursor in the text, if the text is michael@0: // selected and if a button is currently pushed (aButtonPushed > 0). michael@0: // The function returns a promise which returns true if the popup opened or michael@0: // false if it didn't michael@0: function intendOpenVarPopup(aPanel, aPosition, aButtonPushed) { michael@0: let bubble = aPanel.panelWin.DebuggerView.VariableBubble; michael@0: let editor = aPanel.panelWin.DebuggerView.editor; michael@0: let tooltip = bubble._tooltip; michael@0: michael@0: let { left, top } = editor.getCoordsFromPosition(aPosition); michael@0: michael@0: const eventDescriptor = { michael@0: clientX: left, michael@0: clientY: top, michael@0: buttons: aButtonPushed michael@0: }; michael@0: michael@0: bubble._onMouseMove(eventDescriptor); michael@0: michael@0: const deferred = promise.defer(); michael@0: window.setTimeout( michael@0: function() { michael@0: if(tooltip.isEmpty()) { michael@0: deferred.resolve(false); michael@0: } else { michael@0: deferred.resolve(true); michael@0: } michael@0: }, michael@0: tooltip.defaultShowDelay + 1000 michael@0: ); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function hideVarPopup(aPanel) { michael@0: let bubble = aPanel.panelWin.DebuggerView.VariableBubble; michael@0: let tooltip = bubble._tooltip.panel; michael@0: michael@0: let popupHiding = once(tooltip, "popuphiding"); michael@0: bubble.hideContents(); michael@0: return popupHiding.then(waitForTick); michael@0: } michael@0: michael@0: function hideVarPopupByScrollingEditor(aPanel) { michael@0: let editor = aPanel.panelWin.DebuggerView.editor; michael@0: let bubble = aPanel.panelWin.DebuggerView.VariableBubble; michael@0: let tooltip = bubble._tooltip.panel; michael@0: michael@0: let popupHiding = once(tooltip, "popuphiding"); michael@0: editor.setFirstVisibleLine(0); michael@0: return popupHiding.then(waitForTick); michael@0: } michael@0: michael@0: function reopenVarPopup(...aArgs) { michael@0: return hideVarPopup.apply(this, aArgs).then(() => openVarPopup.apply(this, aArgs)); michael@0: } michael@0: michael@0: // Tracing helpers michael@0: michael@0: function startTracing(aPanel) { michael@0: const deferred = promise.defer(); michael@0: aPanel.panelWin.DebuggerController.Tracer.startTracing(aResponse => { michael@0: if (aResponse.error) { michael@0: deferred.reject(aResponse); michael@0: } else { michael@0: deferred.resolve(aResponse); michael@0: } michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function stopTracing(aPanel) { michael@0: const deferred = promise.defer(); michael@0: aPanel.panelWin.DebuggerController.Tracer.stopTracing(aResponse => { michael@0: if (aResponse.error) { michael@0: deferred.reject(aResponse); michael@0: } else { michael@0: deferred.resolve(aResponse); michael@0: } michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function filterTraces(aPanel, f) { michael@0: const traces = aPanel.panelWin.document michael@0: .getElementById("tracer-traces") michael@0: .querySelector("scrollbox") michael@0: .children; michael@0: return Array.filter(traces, f); michael@0: } michael@0: function attachAddonActorForUrl(aClient, aUrl) { michael@0: let deferred = promise.defer(); michael@0: michael@0: getAddonActorForUrl(aClient, aUrl).then(aGrip => { michael@0: aClient.attachAddon(aGrip.actor, aResponse => { michael@0: deferred.resolve([aGrip, aResponse]); michael@0: }); michael@0: }); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function rdpInvoke(aClient, aMethod, ...args) { michael@0: return promiseInvoke(aClient, aMethod, ...args) michael@0: .then(({error, message }) => { michael@0: if (error) { michael@0: throw new Error(error + ": " + message); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: function doResume(aPanel) { michael@0: const threadClient = aPanel.panelWin.gThreadClient; michael@0: return rdpInvoke(threadClient, threadClient.resume); michael@0: } michael@0: michael@0: function doInterrupt(aPanel) { michael@0: const threadClient = aPanel.panelWin.gThreadClient; michael@0: return rdpInvoke(threadClient, threadClient.interrupt); michael@0: } michael@0: