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