forked from mirrors/gecko-dev
Remove remaining GCLI code Differential Revision: https://phabricator.services.mozilla.com/D3618 --HG-- extra : moz-landing-system : lando
1316 lines
39 KiB
JavaScript
1316 lines
39 KiB
JavaScript
/* -*- 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);
|
|
|
|
var { BrowserToolboxProcess } = ChromeUtils.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
|
|
var { DebuggerServer } = require("devtools/server/main");
|
|
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 = getRootDirectory(gTestPath) + "code_frame-script.js";
|
|
const CHROME_URL = "chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/";
|
|
const CHROME_URI = Services.io.newURI(CHROME_URL);
|
|
|
|
Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false);
|
|
|
|
registerCleanupFunction(async function() {
|
|
Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend");
|
|
|
|
info("finish() was called, cleaning up...");
|
|
Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
|
|
|
|
while (gBrowser && gBrowser.tabs && gBrowser.tabs.length > 1) {
|
|
info("Destroying toolbox.");
|
|
let target = 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;
|
|
}
|
|
|
|
function getTargetActorForUrl(aClient, aUrl) {
|
|
let deferred = promise.defer();
|
|
|
|
aClient.listTabs().then(aResponse => {
|
|
let targetActor = aResponse.tabs.filter(aGrip => aGrip.url == aUrl).pop();
|
|
deferred.resolve(targetActor);
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
function getAddonActorForId(aClient, aAddonId) {
|
|
info("Get addon actor for ID: " + aAddonId);
|
|
let deferred = promise.defer();
|
|
|
|
aClient.listAddons(aResponse => {
|
|
let addonTargetActor = aResponse.addons.filter(aGrip => aGrip.id == aAddonId).pop();
|
|
info("got addon actor for ID: " + aAddonId);
|
|
deferred.resolve(addonTargetActor);
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
async function attachTargetActorForUrl(aClient, aUrl) {
|
|
let grip = await getTargetActorForUrl(aClient, aUrl);
|
|
let [ response ] = await aClient.attachTab(grip.actor);
|
|
return [grip, response];
|
|
}
|
|
|
|
async function attachThreadActorForUrl(aClient, aUrl) {
|
|
let [grip, response] = await attachTargetActorForUrl(aClient, aUrl);
|
|
let [response2, threadClient] = await aClient.attachThread(response.threadActor);
|
|
await threadClient.resume();
|
|
return threadClient;
|
|
}
|
|
|
|
// 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(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 <xul:tab>, 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 debuggee = tab.linkedBrowser.contentWindowAsCPOW.wrappedJSObject;
|
|
let target = 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, debuggee, debuggerPanel, window];
|
|
});
|
|
|
|
// Creates an add-on debugger for a given add-on. The returned AddonDebugger
|
|
// object must be destroyed before finishing the test
|
|
function initAddonDebugger(aAddonId) {
|
|
let addonDebugger = new AddonDebugger();
|
|
return addonDebugger.init(aAddonId).then(() => addonDebugger);
|
|
}
|
|
|
|
function AddonDebugger() {
|
|
this._onMessage = this._onMessage.bind(this);
|
|
this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
|
|
EventEmitter.decorate(this);
|
|
}
|
|
|
|
AddonDebugger.prototype = {
|
|
init: Task.async(function* (aAddonId) {
|
|
info("Initializing an addon debugger panel.");
|
|
|
|
DebuggerServer.init();
|
|
DebuggerServer.registerAllActors();
|
|
DebuggerServer.allowChromeProcess = true;
|
|
|
|
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);
|
|
|
|
yield this.client.connect();
|
|
|
|
let addonTargetActor = yield getAddonActorForId(this.client, aAddonId);
|
|
|
|
let targetOptions = {
|
|
form: addonTargetActor,
|
|
client: this.client,
|
|
chrome: true,
|
|
isBrowsingContext: false
|
|
};
|
|
|
|
let toolboxOptions = {
|
|
customIframe: this.frame
|
|
};
|
|
|
|
this.target = TargetFactory.forTab(targetOptions);
|
|
let toolbox = yield gDevTools.showToolbox(this.target, "jsdebugger", Toolbox.HostType.CUSTOM, toolboxOptions);
|
|
|
|
info("Addon debugger panel shown successfully.");
|
|
|
|
this.debuggerPanel = toolbox.getCurrentPanel();
|
|
yield waitForSourceShown(this.debuggerPanel, "");
|
|
|
|
prepareDebugger(this.debuggerPanel);
|
|
yield this._attachConsole();
|
|
}),
|
|
|
|
destroy: Task.async(function* () {
|
|
yield this.client.close();
|
|
yield this.debuggerPanel._toolbox.destroy();
|
|
this.frame.remove();
|
|
window.removeEventListener("message", this._onMessage);
|
|
}),
|
|
|
|
_attachConsole: function () {
|
|
let deferred = promise.defer();
|
|
this.client.attachConsole(this.target.form.consoleActor, ["ConsoleAPI"])
|
|
.then(([aResponse, aWebConsoleClient]) => {
|
|
this.webConsole = aWebConsoleClient;
|
|
this.client.addListener("consoleAPICall", this._onConsoleAPICall);
|
|
deferred.resolve();
|
|
}, e => {
|
|
deferred.reject(e);
|
|
});
|
|
return deferred.promise;
|
|
},
|
|
|
|
_onConsoleAPICall: function (aType, aPacket) {
|
|
if (aPacket.from != this.webConsole.actor)
|
|
return;
|
|
this.emit("console", aPacket.message);
|
|
},
|
|
|
|
/**
|
|
* 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.actor).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) {
|
|
if (!event.data) {
|
|
return;
|
|
}
|
|
const msg = event.data;
|
|
switch (msg.name) {
|
|
case "toolbox-title":
|
|
this.title = msg.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, 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 attachAddonActorForId(aClient, aAddonId) {
|
|
let deferred = promise.defer();
|
|
|
|
getAddonActorForId(aClient, aAddonId).then(aGrip => {
|
|
aClient.attachAddon(aGrip.actor).then(([aResponse]) => {
|
|
deferred.resolve([aGrip, aResponse]);
|
|
});
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
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 attachTab(client, tab) {
|
|
info("Attaching to tab with url '" + tab.url + "'.");
|
|
return client.attachTab(tab.actor);
|
|
}
|
|
|
|
function listWorkers(tabClient) {
|
|
info("Listing workers.");
|
|
return new Promise(function (resolve) {
|
|
tabClient.listWorkers(function (response) {
|
|
resolve(response);
|
|
});
|
|
});
|
|
}
|
|
|
|
function findWorker(workers, url) {
|
|
info("Finding worker with url '" + url + "'.");
|
|
for (let worker of workers) {
|
|
if (worker.url === url) {
|
|
return worker;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function attachWorker(tabClient, worker) {
|
|
info("Attaching to worker with url '" + worker.url + "'.");
|
|
return tabClient.attachWorker(worker.actor);
|
|
}
|
|
|
|
function waitForWorkerListChanged(tabClient) {
|
|
info("Waiting for worker list to change.");
|
|
return new Promise(function (resolve) {
|
|
tabClient.addListener("workerListChanged", function listener() {
|
|
tabClient.removeListener("workerListChanged", listener);
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
function attachThread(workerClient, options) {
|
|
info("Attaching to thread.");
|
|
return workerClient.attachThread(options);
|
|
}
|
|
|
|
function waitForWorkerClose(workerClient) {
|
|
info("Waiting for worker to close.");
|
|
return new Promise(function (resolve) {
|
|
workerClient.addOneTimeListener("close", function () {
|
|
info("Worker did close.");
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
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)");
|
|
}
|
|
});
|
|
}
|
|
|
|
async function initWorkerDebugger(TAB_URL, WORKER_URL) {
|
|
DebuggerServer.init();
|
|
DebuggerServer.registerAllActors();
|
|
|
|
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
|
await connect(client);
|
|
|
|
let tab = await addTab(TAB_URL);
|
|
let { tabs } = await listTabs(client);
|
|
let [, tabClient] = await attachTab(client, findTab(tabs, TAB_URL));
|
|
|
|
await createWorkerInTab(tab, WORKER_URL);
|
|
|
|
let { workers } = await listWorkers(tabClient);
|
|
let [, workerClient] = await attachWorker(tabClient,
|
|
findWorker(workers, WORKER_URL));
|
|
|
|
let toolbox = await gDevTools.showToolbox(TargetFactory.forWorker(workerClient),
|
|
"jsdebugger",
|
|
Toolbox.HostType.WINDOW);
|
|
|
|
let debuggerPanel = toolbox.getCurrentPanel();
|
|
let gDebugger = debuggerPanel.panelWin;
|
|
|
|
return {client, tab, tabClient, workerClient, toolbox, gDebugger};
|
|
}
|