diff --git a/devtools/client/debugger/moz.build b/devtools/client/debugger/moz.build index 30b945738750..87c48db0b915 100644 --- a/devtools/client/debugger/moz.build +++ b/devtools/client/debugger/moz.build @@ -9,7 +9,6 @@ DIRS += [ BROWSER_CHROME_MANIFESTS += [ 'new/test/mochitest/browser.ini', - 'test/mochitest/browser.ini' ] with Files('**'): diff --git a/devtools/client/debugger/test/.eslintrc.js b/devtools/client/debugger/test/.eslintrc.js deleted file mode 100644 index 8d15a76d9b8c..000000000000 --- a/devtools/client/debugger/test/.eslintrc.js +++ /dev/null @@ -1,6 +0,0 @@ -"use strict"; - -module.exports = { - // Extend from the shared list of defined globals for mochitests. - "extends": "../../../.eslintrc.mochitests.js" -}; diff --git a/devtools/client/debugger/test/mochitest/browser.ini b/devtools/client/debugger/test/mochitest/browser.ini deleted file mode 100644 index 4e08e44b3700..000000000000 --- a/devtools/client/debugger/test/mochitest/browser.ini +++ /dev/null @@ -1,12 +0,0 @@ -[DEFAULT] -tags = devtools -subsuite = devtools -skip-if = (os == 'linux' && debug && bits == 32) -support-files = - doc_terminate-on-tab-close.html - head.js - !/devtools/client/shared/test/shared-head.js - !/devtools/client/shared/test/telemetry-test-helpers.js -[browser_dbg_terminate-on-tab-close.js] -uses-unsafe-cpows = true -skip-if = true diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_terminate-on-tab-close.js b/devtools/client/debugger/test/mochitest/browser_dbg_terminate-on-tab-close.js deleted file mode 100644 index f6311ea3f5fd..000000000000 --- a/devtools/client/debugger/test/mochitest/browser_dbg_terminate-on-tab-close.js +++ /dev/null @@ -1,34 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -/** - * Tests that debuggee scripts are terminated on tab closure. - */ - -// The following rejection should not be left uncaught. This test has been -// whitelisted until the issue is fixed. -if (!gMultiProcessBrowser) { - ChromeUtils.import("resource://testing-common/PromiseTestUtils.jsm", this); - PromiseTestUtils.expectUncaughtRejection(/error\.message is undefined/); -} - -const TAB_URL = EXAMPLE_URL + "doc_terminate-on-tab-close.html"; - -add_task(async () => { - const options = { - source: TAB_URL, - line: 1 - }; - const { tab, panel } = await initDebugger(TAB_URL, options); - const { gThreadClient } = panel.panelWin; - - gThreadClient.addOneTimeListener("paused", () => { - resumeDebuggerThenCloseAndFinish(gPanel).then(function () { - ok(true, "should not throw after this point"); - }); - }); - - callInTab(gTab, "debuggerThenThrow"); -}); diff --git a/devtools/client/debugger/test/mochitest/doc_terminate-on-tab-close.html b/devtools/client/debugger/test/mochitest/doc_terminate-on-tab-close.html deleted file mode 100644 index 2101b31034db..000000000000 --- a/devtools/client/debugger/test/mochitest/doc_terminate-on-tab-close.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - Debugger test page - - - - - - - diff --git a/devtools/client/debugger/test/mochitest/head.js b/devtools/client/debugger/test/mochitest/head.js deleted file mode 100644 index 6126e24c138a..000000000000 --- a/devtools/client/debugger/test/mochitest/head.js +++ /dev/null @@ -1,1068 +0,0 @@ - /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// shared-head.js handles imports, constants, and utility functions -Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", this); - -// 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. -var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log"); -Services.prefs.setBoolPref("devtools.debugger.log", false); -Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); - -var { BrowserToolboxProcess } = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {}); -var { DebuggerServer } = require("devtools/server/main"); -var { ActorRegistry } = require("devtools/server/actors/utils/actor-registry"); -var { DebuggerClient } = require("devtools/shared/client/debugger-client"); -var ObjectClient = require("devtools/shared/client/object-client"); -var { AddonManager } = ChromeUtils.import("resource://gre/modules/AddonManager.jsm", {}); -var EventEmitter = require("devtools/shared/event-emitter"); -var { Toolbox } = require("devtools/client/framework/toolbox"); -var { Task } = require("devtools/shared/task"); - -const chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry); - -// Override promise with deprecated-sync-thenables -promise = require("devtools/shared/deprecated-sync-thenables"); - -const EXAMPLE_URL = "http://example.com/browser/devtools/client/debugger/test/mochitest/"; -const FRAME_SCRIPT_URL = "chrome://mochitests/content/browser/devtools/client/shared/test/code_frame-script.js"; -const CHROME_URL = "chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/"; -const CHROME_URI = Services.io.newURI(CHROME_URL); - -registerCleanupFunction(async function() { - info("finish() was called, cleaning up..."); - Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging); - Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); - - while (gBrowser && gBrowser.tabs && gBrowser.tabs.length > 1) { - info("Destroying toolbox."); - let target = await TargetFactory.forTab(gBrowser.selectedTab); - await gDevTools.closeToolbox(target); - - info("Removing tab."); - gBrowser.removeCurrentTab(); - } - - // 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/CC after debugger test."); - await new Promise(resolve => { - Cu.forceGC(); - Cu.forceCC(); - Cu.schedulePreciseGC(resolve); - }); -}); - -var testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); -testDir = testDir.replace(/\/\//g, "/"); -testDir = testDir.replace("chrome:/mochitest", "chrome://mochitest"); - -function addWindow(aUrl) { - info("Adding window: " + aUrl); - return promise.resolve(getChromeWindow(window.open(aUrl))); -} - -function getChromeWindow(aWindow) { - return aWindow.docShell.rootTreeItem.domWindow; -} - -// Override addTab/removeTab as defined by shared-head, since these have -// an extra window parameter and add a frame script -this.addTab = 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 = BrowserTestUtils.addTab(targetBrowser, aUrl); - let linkedBrowser = tab.linkedBrowser; - - info("Loading frame script with url " + FRAME_SCRIPT_URL + "."); - linkedBrowser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false); - - BrowserTestUtils.browserLoaded(linkedBrowser) - .then(function () { - info("Tab added and finished loading: " + aUrl); - deferred.resolve(tab); - }); - - return deferred.promise; -}; - -this.removeTab = 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 (aEvent) { - info("Tab removed and finished closing."); - deferred.resolve(); - }, {once: true}); - - targetBrowser.removeTab(aTab); - return deferred.promise; -}; - -function getAddonURIFromPath(aPath) { - let chromeURI = Services.io.newURI(aPath, null, CHROME_URI); - return chromeRegistry.convertChromeURL(chromeURI).QueryInterface(Ci.nsIFileURL); -} - -function getTemporaryAddonURLFromPath(aPath) { - return getAddonURIFromPath(aPath).spec; -} - -function addTemporaryAddon(aPath) { - let addonFile = getAddonURIFromPath(aPath).file; - info("Installing addon: " + addonFile.path); - - return AddonManager.installTemporaryAddon(addonFile); -} - -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; -} - -// Override once from shared-head, as some tests depend on trying native DOM listeners -// before EventEmitter. Since this directory is deprecated, there's little value in -// resolving the descrepency here. -this.once = function (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 waitForSourceLoaded(aPanel, aUrl) { - let { Sources } = aPanel.panelWin.DebuggerView; - let isLoaded = Sources.items.some(item => - item.attachment.source.url === aUrl); - if (isLoaded) { - info("The correct source has been loaded."); - return promise.resolve(null); - } else { - return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.NEW_SOURCE).then(() => { - // Wait for it to be loaded in the UI and appear into Sources.items. - return waitForTick(); - }).then(() => { - return waitForSourceLoaded(aPanel, aUrl); - }); - } - -} - -function waitForSourceShown(aPanel, aUrl) { - return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.SOURCE_SHOWN).then(aSource => { - let sourceUrl = aSource.url || aSource.introductionUrl; - info("Source shown: " + sourceUrl); - - if (!sourceUrl.includes(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, aUrlOrSource, aWaitFlag = false) { - let sources = aPanel.panelWin.DebuggerView.Sources; - - if (sources.selectedValue === aUrlOrSource || - (sources.selectedItem && - sources.selectedItem.attachment.source.url.includes(aUrlOrSource))) { - ok(true, "Expected source is shown: " + aUrlOrSource); - return promise.resolve(null); - } - if (aWaitFlag) { - return waitForSourceShown(aPanel, aUrlOrSource); - } - ok(false, "Expected source was not already shown: " + aUrlOrSource); - 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 isDebugPos(aPanel, aLine) { - let editor = aPanel.panelWin.DebuggerView.editor; - let location = editor.getDebugLocation(); - - // Source editor starts counting line and column numbers from 0. - info("Current editor debug position: " + (location + 1)); - return location != null && editor.hasLineClass(aLine - 1, "debug-line"); -} - -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(...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 reload(aPanel, aUrl) { - let activeTab = aPanel.panelWin.DebuggerController._target.activeTab; - aUrl ? activeTab.navigateTo({ url: aUrl }) : activeTab.reload(); -} - -function navigateActiveTabTo(aPanel, aUrl, aWaitForEventName, aEventRepeat) { - let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat); - reload(aPanel, aUrl); - 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) { - info("Getting sources."); - - let deferred = promise.defer(); - - aClient.getSources((packet) => { - deferred.resolve(packet.sources); - }); - - return deferred.promise; -} - -/** - * Optionaly open a new tab and then open the debugger panel. - * The returned promise resolves only one the panel is fully set. - - * @param {String|xul:tab} urlOrTab - * If a string, consider it as the url of the tab to open before opening the - * debugger panel. - * Otherwise, if a , do nothing, but open the debugger panel against - * the given tab. - * @param {Object} options - * Set of optional arguments: - * - {String} source - * If given, assert the default loaded source once the debugger is loaded. - * This string can be partial to only match a part of the source name. - * If null, do not expect any source and skip SOURCE_SHOWN wait. - * - {Number} line - * If given, wait for the caret to be set on a precise line - * - * @return {Promise} - * Resolves once debugger panel is fully set according to the given options. - */ -let initDebugger = Task.async(function*(urlOrTab, options) { - let { window, source, line } = options || {}; - info("Initializing a debugger panel."); - - let tab, url; - if (urlOrTab instanceof XULElement) { - // `urlOrTab` Is a Tab. - tab = urlOrTab; - } else { - // `urlOrTab` is an url. Open an empty tab first in order to load the page - // only once the panel is ready. That to be able to safely catch the - // SOURCE_SHOWN event. - tab = yield addTab("about:blank", window); - url = urlOrTab; - } - info("Debugee tab added successfully: " + urlOrTab); - - let target = yield TargetFactory.forTab(tab); - - let toolbox = yield gDevTools.showToolbox(target, "jsdebugger"); - info("Debugger panel shown successfully."); - - let debuggerPanel = toolbox.getCurrentPanel(); - let panelWin = debuggerPanel.panelWin; - let { Sources } = panelWin.DebuggerView; - - prepareDebugger(debuggerPanel); - - if (url && url != "about:blank") { - let onCaretUpdated; - if (line) { - onCaretUpdated = waitForCaretUpdated(debuggerPanel, line); - } - if (source === null) { - // When there is no source in the document, we shouldn't wait for - // SOURCE_SHOWN event - yield reload(debuggerPanel, url); - } else { - yield navigateActiveTabTo(debuggerPanel, - url, - panelWin.EVENTS.SOURCE_SHOWN); - } - if (source) { - let isSelected = Sources.selectedItem.attachment.source.url === source; - if (!isSelected) { - // Ensure that the source is loaded first before trying to select it - yield waitForSourceLoaded(debuggerPanel, source); - // Select the js file. - let onSource = waitForSourceAndCaret(debuggerPanel, source, line ? line : 1); - Sources.selectedValue = getSourceActor(Sources, source); - yield onSource; - } - } - yield onCaretUpdated; - } - - return [tab, debuggerPanel, window]; -}); - -function initChromeDebugger(aOnClose) { - info("Initializing a chrome debugger process."); - - let deferred = promise.defer(); - - // Wait for the toolbox process to start... - BrowserToolboxProcess.init(aOnClose, 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.Filtering.FilteredSources._autoSelectFirstItem = true; - view.Filtering.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"); -} - -/** - * Returns the node that has the black-boxed class applied to it. - */ -function getSelectedSourceElement(aPanel) { - return aPanel.panelWin.DebuggerView.Sources.selectedItem.prebuiltNode; -} - -function toggleBlackBoxing(aPanel, aSourceActor = null) { - function clickBlackBoxButton() { - getBlackBoxButton(aPanel).click(); - } - - const blackBoxChanged = waitForDispatch( - aPanel, - aPanel.panelWin.constants.BLACKBOX - ).then(() => { - return aSourceActor ? - getSource(aPanel, aSourceActor) : - getSelectedSource(aPanel); - }); - - if (aSourceActor) { - aPanel.panelWin.DebuggerView.Sources.selectedValue = aSourceActor; - ensureSourceIs(aPanel, aSourceActor, true).then(clickBlackBoxButton); - } else { - clickBlackBoxButton(); - } - - return blackBoxChanged; -} - -function selectSourceAndGetBlackBoxButton(aPanel, aUrl) { - function returnBlackboxButton() { - return getBlackBoxButton(aPanel); - } - - let sources = aPanel.panelWin.DebuggerView.Sources; - sources.selectedValue = getSourceActor(sources, aUrl); - return ensureSourceIs(aPanel, aUrl, 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 updatedFrame = waitForDebuggerEvents(aPanel, events.FETCHED_SCOPES); - - let { left, top } = editor.getCoordsFromPosition(aCoords); - bubble._findIdentifier(left, top); - return promise.all([popupShown, fetchedProperties, updatedFrame]).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); - } - }, - bubble.TOOLTIP_SHOW_DELAY + 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)); -} - -function doResume(aPanel) { - const threadClient = aPanel.panelWin.gThreadClient; - return threadClient.resume(); -} - -function doInterrupt(aPanel) { - const threadClient = aPanel.panelWin.gThreadClient; - return threadClient.interrupt(); -} - -function pushPrefs(...aPrefs) { - let deferred = promise.defer(); - SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve); - return deferred.promise; -} - -function popPrefs() { - let deferred = promise.defer(); - SpecialPowers.popPrefEnv(deferred.resolve); - return deferred.promise; -} - -// Source helpers - -function getSelectedSource(panel) { - const win = panel.panelWin; - return win.queries.getSelectedSource(win.DebuggerController.getState()); -} - -function getSource(panel, actor) { - const win = panel.panelWin; - return win.queries.getSource(win.DebuggerController.getState(), actor); -} - -function getSelectedSourceURL(aSources) { - return (aSources.selectedItem && - aSources.selectedItem.attachment.source.url); -} - -function getSourceURL(aSources, aActor) { - let item = aSources.getItemByValue(aActor); - return item && item.attachment.source.url; -} - -function getSourceActor(aSources, aURL) { - let item = aSources.getItemForAttachment(a => a.source && a.source.url === aURL); - return item && item.value; -} - -function getSourceForm(aSources, aURL) { - let item = aSources.getItemByValue(getSourceActor(aSources, aURL)); - return item.attachment.source; -} - -var nextId = 0; - -function jsonrpc(tab, method, params) { - return new Promise(function (resolve, reject) { - let currentId = nextId++; - let messageManager = tab.linkedBrowser.messageManager; - messageManager.sendAsyncMessage("jsonrpc", { - method: method, - params: params, - id: currentId - }); - messageManager.addMessageListener("jsonrpc", function listener(res) { - const { data: { result, error, id } } = res; - if (id !== currentId) { - return; - } - - messageManager.removeMessageListener("jsonrpc", listener); - if (error != null) { - reject(error); - } - - resolve(result); - }); - }); -} - -function callInTab(tab, name) { - info("Calling function with name '" + name + "' in tab."); - - return jsonrpc(tab, "call", [name, Array.prototype.slice.call(arguments, 2)]); -} - -function evalInTab(tab, string) { - info("Evalling string in tab."); - - return jsonrpc(tab, "_eval", [string]); -} - -function createWorkerInTab(tab, url) { - info("Creating worker with url '" + url + "' in tab."); - - return jsonrpc(tab, "createWorker", [url]); -} - -function terminateWorkerInTab(tab, url) { - info("Terminating worker with url '" + url + "' in tab."); - - return jsonrpc(tab, "terminateWorker", [url]); -} - -function postMessageToWorkerInTab(tab, url, message) { - info("Posting message to worker with url '" + url + "' in tab."); - - return jsonrpc(tab, "postMessageToWorker", [url, message]); -} - -function generateMouseClickInTab(tab, path) { - info("Generating mouse click in tab."); - - return jsonrpc(tab, "generateMouseClick", [path]); -} - -function connect(client) { - info("Connecting client."); - return client.connect(); -} - -function close(client) { - info("Waiting for client to close.\n"); - return client.close(); -} - -function listTabs(client) { - info("Listing tabs."); - return client.listTabs(); -} - -function findTab(tabs, url) { - info("Finding tab with url '" + url + "'."); - for (let tab of tabs) { - if (tab.url === url) { - return tab; - } - } - return null; -} - -function attachTarget(client, tab) { - info("Attaching to tab with url '" + tab.url + "'."); - return client.attachTarget(tab); -} - -function listWorkers(targetFront) { - info("Listing workers."); - return targetFront.listWorkers(); -} - -function findWorker(workers, url) { - info("Finding worker with url '" + url + "'."); - for (let worker of workers) { - if (worker.url === url) { - return worker; - } - } - return null; -} - -function waitForWorkerListChanged(targetFront) { - info("Waiting for worker list to change."); - return targetFront.once("workerListChanged"); -} - -function attachThread(workerTargetFront, options) { - info("Attaching to thread."); - return workerTargetFront.attachThread(options); -} - -async function waitForWorkerClose(workerTargetFront) { - info("Waiting for worker to close."); - await workerTargetFront.once("close"); - info("Worker did close."); -} - -function resume(threadClient) { - info("Resuming thread."); - return threadClient.resume(); -} - -function findSource(sources, url) { - info("Finding source with url '" + url + "'.\n"); - for (let source of sources) { - if (source.url === url) { - return source; - } - } - return null; -} - -function waitForEvent(client, type, predicate) { - return new Promise(function (resolve) { - function listener(type, packet) { - if (!predicate(packet)) { - return; - } - client.removeListener(listener); - resolve(packet); - } - - if (predicate) { - client.addListener(type, listener); - } else { - client.addOneTimeListener(type, function (type, packet) { - resolve(packet); - }); - } - }); -} - -function waitForPause(threadClient) { - info("Waiting for pause.\n"); - return waitForEvent(threadClient, "paused"); -} - -function setBreakpoint(sourceClient, location) { - info("Setting breakpoint.\n"); - return sourceClient.setBreakpoint(location); -} - -function source(sourceClient) { - info("Getting source.\n"); - return sourceClient.source(); -} - -// Return a promise with a reference to jsterm, opening the split -// console if necessary. This cleans up the split console pref so -// it won't pollute other tests. -function getSplitConsole(toolbox, win) { - if (!win) { - win = toolbox.win; - } - - if (!toolbox.splitConsole) { - EventUtils.synthesizeKey("VK_ESCAPE", {}, win); - } - - return new Promise(resolve => { - toolbox.getPanelWhenReady("webconsole").then(() => { - ok(toolbox.splitConsole, "Split console is shown."); - let jsterm = toolbox.getPanel("webconsole").hud.jsterm; - resolve(jsterm); - }); - }); -} - -// navigation - -function waitForNavigation(gPanel) { - const target = gPanel.panelWin.gTarget; - const deferred = promise.defer(); - target.once("navigate", () => { - deferred.resolve(); - }); - info("Waiting for navigation..."); - return deferred.promise; -} - -// actions - -function bindActionCreators(panel) { - const win = panel.panelWin; - const dispatch = win.DebuggerController.dispatch; - const { bindActionCreators } = win.require("devtools/client/shared/vendor/redux"); - return bindActionCreators(win.actions, dispatch); -} - -// Wait until an action of `type` is dispatched. This is different -// then `_afterDispatchDone` because it doesn't wait for async actions -// to be done/errored. Use this if you want to listen for the "start" -// action of an async operation (somewhat rare). -function waitForNextDispatch(store, type) { - return new Promise(resolve => { - store.dispatch({ - // Normally we would use `services.WAIT_UNTIL`, but use the - // internal name here so tests aren't forced to always pass it - // in - type: "@@service/waitUntil", - predicate: action => action.type === type, - run: (dispatch, getState, action) => { - resolve(action); - } - }); - }); -} - -// Wait until an action of `type` is dispatched. If it's part of an -// async operation, wait until the `status` field is "done" or "error" -function _afterDispatchDone(store, type) { - return new Promise(resolve => { - store.dispatch({ - // Normally we would use `services.WAIT_UNTIL`, but use the - // internal name here so tests aren't forced to always pass it - // in - type: "@@service/waitUntil", - predicate: action => { - if (action.type === type) { - return action.status ? - (action.status === "done" || action.status === "error") : - true; - } - }, - run: (dispatch, getState, action) => { - resolve(action); - } - }); - }); -} - -function waitForDispatch(panel, type, eventRepeat = 1) { - const controller = panel.panelWin.DebuggerController; - const actionType = panel.panelWin.constants[type]; - let count = 0; - - return Task.spawn(function* () { - info("Waiting for " + type + " to dispatch " + eventRepeat + " time(s)"); - while (count < eventRepeat) { - yield _afterDispatchDone(controller, actionType); - count++; - info(type + " dispatched " + count + " time(s)"); - } - }); -}