fune/devtools/client/debugger/test/mochitest/head.js
yulia 9611217838 Bug 1429421 - remove gcli code; r=jdescottes,julienw,ochameau
Remove remaining GCLI code

Differential Revision: https://phabricator.services.mozilla.com/D3618

--HG--
extra : moz-landing-system : lando
2018-09-06 11:42:57 +00:00

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};
}