Files
tubestation/browser/devtools/debugger/test/head.js
Ryan VanderMeulen c633e65783 Backed out 6 changesets (bug 663778) for browser_dbg_listtabs-02.js leaks on a CLOSED TREE.
Backed out changeset cbd200206f9b (bug 663778)
Backed out changeset b0b74401e614 (bug 663778)
Backed out changeset df5255cffbfa (bug 663778)
Backed out changeset e6d0dd124356 (bug 663778)
Backed out changeset 134f77474941 (bug 663778)
Backed out changeset 456609873c5f (bug 663778)
2014-03-13 13:56:22 -04:00

710 lines
21 KiB
JavaScript

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