Bug 876277 - Rewrite head.js to use promises and remove useless cruft, r=past

This commit is contained in:
Victor Porof 2013-09-13 16:23:16 +03:00
parent a33ccba979
commit a0d28d5edf
2 changed files with 413 additions and 170 deletions

View File

@ -1714,12 +1714,6 @@ DebuggerController.Breakpoints = new Breakpoints();
* Export some properties to the global scope for easier access.
*/
Object.defineProperties(window, {
"dispatchEvent": {
get: function() ViewHelpers.dispatchEvent,
},
"editor": {
get: function() DebuggerView.editor
},
"gTarget": {
get: function() DebuggerController._target
},
@ -1729,18 +1723,6 @@ Object.defineProperties(window, {
"gThreadClient": {
get: function() DebuggerController.activeThread
},
"gThreadState": {
get: function() DebuggerController.ThreadState
},
"gStackFrames": {
get: function() DebuggerController.StackFrames
},
"gSourceScripts": {
get: function() DebuggerController.SourceScripts
},
"gBreakpoints": {
get: function() DebuggerController.Breakpoints
},
"gCallStackPageSize": {
get: function() CALL_STACK_PAGE_SIZE
}

View File

@ -2,48 +2,45 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
let tempScope = {};
Cu.import("resource://gre/modules/Services.jsm", tempScope);
let Services = tempScope.Services;
// Disable logging for faster test runs. Set this pref to true if you want to
// debug a test in your try runs.
// 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);
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
Cu.import("resource://gre/modules/devtools/dbg-client.jsm", tempScope);
Cu.import("resource:///modules/source-editor.jsm", tempScope);
Cu.import("resource:///modules/devtools/gDevTools.jsm", tempScope);
Cu.import("resource://gre/modules/devtools/Loader.jsm", tempScope);
Cu.import("resource://gre/modules/AddonManager.jsm", tempScope);
let SourceEditor = tempScope.SourceEditor;
let DebuggerServer = tempScope.DebuggerServer;
let DebuggerTransport = tempScope.DebuggerTransport;
let DebuggerClient = tempScope.DebuggerClient;
let AddonManager = tempScope.AddonManager;
let gDevTools = tempScope.gDevTools;
let devtools = tempScope.devtools;
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 { BrowserDebuggerProcess } = Cu.import("resource:///modules/devtools/DebuggerProcess.jsm", {});
let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
let { SourceEditor } = Cu.import("resource:///modules/source-editor.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();
});
// Import the GCLI test helper
let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/";
const TAB1_URL = EXAMPLE_URL + "browser_dbg_tab1.html";
const TAB2_URL = EXAMPLE_URL + "browser_dbg_tab2.html";
const ADDON1_URL = EXAMPLE_URL + "browser_dbg_addon1.xpi";
const ADDON2_URL = EXAMPLE_URL + "browser_dbg_addon2.xpi";
const STACK_URL = EXAMPLE_URL + "browser_dbg_stack.html";
// Enable remote debugging for the relevant tests.
let gEnableRemote = Services.prefs.getBoolPref("devtools.debugger.remote-enabled");
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
// Redeclare dbg_assert with a fatal behavior.
function dbg_assert(cond, e) {
if (!cond) {
@ -51,197 +48,461 @@ function dbg_assert(cond, e) {
}
}
registerCleanupFunction(function() {
Services.prefs.setBoolPref("devtools.debugger.remote-enabled", gEnableRemote);
Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
// Properly shut down the server to avoid memory leaks.
DebuggerServer.destroy();
});
if (!DebuggerServer.initialized) {
DebuggerServer.init(function() true);
DebuggerServer.addBrowserActors();
function addWindow(aUrl) {
info("Adding window: " + aUrl);
return promise.resolve(getDOMWindow(window.open(aUrl)));
}
waitForExplicitFinish();
function addWindow() {
let windowReference = window.open();
let chromeWindow = windowReference
function getDOMWindow(aReference) {
return aReference
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
return chromeWindow;
}
function addTab(aURL, aOnload, aWindow) {
function addTab(aUrl, aWindow) {
info("Adding tab: " + aUrl);
let deferred = promise.defer();
let targetWindow = aWindow || window;
let targetBrowser = targetWindow.gBrowser;
targetWindow.focus();
targetBrowser.selectedTab = targetBrowser.addTab(aURL);
let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl);
let linkedBrowser = tab.linkedBrowser;
let tab = targetBrowser.selectedTab;
let browser = tab.linkedBrowser;
let win = browser.contentWindow;
let expectedReadyState = aURL == "about:blank" ? ["interactive", "complete"] : ["complete"];
linkedBrowser.addEventListener("load", function onLoad() {
linkedBrowser.removeEventListener("load", onLoad, true);
deferred.resolve(tab);
}, true);
if (aOnload) {
let handler = function() {
if (browser.currentURI.spec != aURL ||
expectedReadyState.indexOf((win.document || {}).readyState) == -1) {
return;
}
browser.removeEventListener("load", handler, true);
executeSoon(aOnload);
}
browser.addEventListener("load", handler, true);
}
return tab;
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);
deferred.resolve();
}, false);
targetBrowser.removeTab(aTab);
return deferred.promise;
}
function addAddon(aURL, aOnInstallEnded) {
AddonManager.getInstallForURL(aURL, function(aInstall) {
aInstall.install();
var listener = {
function addAddon(aUrl) {
info("Installing addon: " + aUrl);
let deferred = promise.defer();
AddonManager.getInstallForURL(aUrl, aInstaller => {
aInstaller.install();
let listener = {
onInstallEnded: function(aAddon, aAddonInstall) {
aInstall.removeListener(listener);
aOnInstallEnded(aAddonInstall);
aInstaller.removeListener(listener);
deferred.resolve(aAddonInstall);
}
};
aInstall.addListener(listener);
aInstaller.addListener(listener);
}, "application/x-xpinstall");
return deferred.promise;
}
function removeAddon(aAddon, aOnUninstalled) {
var listener = {
function removeAddon(aAddon) {
info("Removing addon.");
let deferred = promise.defer();
let listener = {
onUninstalled: function(aUninstalledAddon) {
if (aUninstalledAddon != aAddon)
if (aUninstalledAddon != aAddon) {
return;
}
AddonManager.removeAddonListener(listener);
aOnUninstalled();
deferred.resolve();
}
};
AddonManager.addAddonListener(listener);
aAddon.uninstall();
return deferred.promise;
}
function closeDebuggerAndFinish(aRemoteFlag, aCallback, aWindow) {
let debuggerClosed = false;
let debuggerDisconnected = false;
function getTabActorForUrl(aClient, aUrl) {
let deferred = promise.defer();
ok(gTab, "There is a gTab to use for getting a toolbox reference");
let target = TargetFactory.forTab(gTab);
window.addEventListener("Debugger:Shutdown", function cleanup() {
window.removeEventListener("Debugger:Shutdown", cleanup, false);
debuggerDisconnected = true;
maybeFinish();
}, false);
let toolbox = gDevTools.getToolbox(target);
toolbox.destroy().then(function() {
debuggerClosed = true;
maybeFinish();
aClient.listTabs(aResponse => {
let tabActor = aResponse.tabs.filter(aGrip => aGrip.url == aUrl).pop();
deferred.resolve(tabActor);
});
function maybeFinish() {
if (debuggerClosed && debuggerDisconnected) {
(finish || aCallback)();
}
}
return deferred.promise;
}
function get_tab_actor_for_url(aClient, aURL, aCallback) {
aClient.listTabs(function(aResponse) {
for each (let tab in aResponse.tabs) {
if (tab.url == aURL) {
aCallback(tab);
return;
}
}
function getAddonActorForUrl(aClient, 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 attach_tab_actor_for_url(aClient, aURL, aCallback) {
get_tab_actor_for_url(aClient, aURL, function(actor) {
aClient.attachTab(actor.actor, function(aResponse) {
aCallback(actor, aResponse);
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 attach_thread_actor_for_url(aClient, aURL, aCallback) {
attach_tab_actor_for_url(aClient, aURL, function(aTabActor, aResponse) {
aClient.attachThread(aResponse.threadActor, function(aResponse, aThreadClient) {
// We don't care about the pause right now (use
// get_actor_for_url() if you do), so resume it.
aThreadClient.resume(function(aResponse) {
aCallback(aThreadClient);
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 wait_for_connect_and_resume(aOnDebugging, aTab) {
let target = TargetFactory.forTab(aTab);
function once(aTarget, aEventName, aUseCapture = false) {
info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
gDevTools.showToolbox(target, "jsdebugger").then(function(toolbox) {
let dbg = toolbox.getCurrentPanel();
let deferred = promise.defer();
// Wait for the initial resume...
dbg.panelWin.gClient.addOneTimeListener("resumed", function() {
aOnDebugging();
});
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 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 debug_tab_pane(aURL, aOnDebugging, aBeforeTabAdded) {
// Make any necessary preparations (start the debugger server etc.)
if (aBeforeTabAdded) {
aBeforeTabAdded();
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);
}
let tab = addTab(aURL, function() {
let debuggee = gBrowser.selectedTab.linkedBrowser.contentWindow.wrappedJSObject;
let target = TargetFactory.forTab(gBrowser.selectedTab);
function waitForCaretUpdated(aPanel, aLine, aCol = 1) {
return waitForEditorEvents(aPanel, SourceEditor.EVENTS.SELECTION).then(() => {
let caret = aPanel.panelWin.DebuggerView.editor.getCaretPosition();
info("Caret updated: " + (caret.line + 1) + ", " + (caret.col + 1));
info("Opening Debugger");
gDevTools.showToolbox(target, "jsdebugger").then(function(toolbox) {
let dbg = toolbox.getCurrentPanel();
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 caret = editor.getCaretPosition();
// Source editor starts counting line and column numbers from 0.
info("Current editor caret position: " + (caret.line + 1) + ", " + (caret.col + 1));
return caret.line == (aLine - 1) && caret.col == (aCol - 1);
}
function isEditorSel(aPanel, [start, end]) {
let editor = aPanel.panelWin.DebuggerView.editor;
let range = editor.getSelection();
// 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.addEventListener(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.removeEventListener(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 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.gClient.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) {
if (aTarget instanceof XULElement) {
return promise.resolve(aTarget);
} else {
return addTab(aTarget);
}
}
function initDebugger(aTarget, aWindow) {
info("Initializing a debugger panel.");
return getTab(aTarget).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...
dbg.panelWin.gClient.addOneTimeListener("resumed", function() {
info("Debugger has started");
dbg._view.Variables.lazyEmpty = false;
dbg._view.Variables.lazyAppend = false;
dbg._view.Variables.lazyExpand = false;
aOnDebugging(tab, debuggee, dbg);
panelWin.gClient.addOneTimeListener("resumed", () => {
info("Debugger client resumed successfully.");
prepareDebugger(debuggerPanel);
deferred.resolve([aTab, debuggee, debuggerPanel]);
});
});
return deferred.promise;
});
}
function debug_chrome(aURL, aOnClosing, aOnDebugging) {
let tab = addTab(aURL, function() {
let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject;
function initChromeDebugger(aOnClose) {
info("Initializing a chrome debugger process.");
info("Opening Browser Debugger");
let win = BrowserDebuggerProcess.init(aOnClosing, function(process) {
let deferred = promise.defer();
// The remote debugging process has started...
info("Browser Debugger has started");
aOnDebugging(tab, debuggee, process);
});
// Wait for the debugger process to start...
BrowserDebuggerProcess.init(aOnClose, aProcess => {
info("Chrome debugger process started successfully.");
prepareDebugger(aProcess);
deferred.resolve(aProcess);
});
return deferred.promise;
}
function prepareDebugger(aDebugger) {
if ("target" in aDebugger) {
let variables = aDebugger.panelWin.DebuggerView.Variables;
variables.lazyEmpty = false;
variables.lazyAppend = false;
variables.lazyExpand = false;
variables.lazySearch = false;
} 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;
}