--- a/devtools/client/framework/test/browser_browser_toolbox_debugger.js
+++ b/devtools/client/framework/test/browser_browser_toolbox_debugger.js
@@ -37,79 +37,226 @@ add_task(function* runTest() {
// Execute the function every second in order to trigger the breakpoint
let interval = setInterval(s.plop, 1000);
// Be careful, this JS function is going to be executed in the browser toolbox,
// which lives in another process. So do not try to use any scope variable!
let env = Components.classes["@mozilla.org/process/environment;1"]
.getService(Components.interfaces.nsIEnvironment);
let testScript = function () {
- const { Task } = Components.utils.import("resource://gre/modules/Task.jsm", {});
- dump("Opening the browser toolbox and debugger panel\n");
+ /* global toolbox */
+
+ const info = msg => dump(msg + "\n");
+
+ info(`START: ${new Error().lineNumber}`)
+
+ const { utils: Cu } = Components;
+ const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+ const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
let window, document;
let testUrl = "http://mozilla.org/browser-toolbox-test.js";
+
+ function createDebuggerContext(toolbox) {
+ const win = toolbox.getPanel("jsdebugger").panelWin;
+ const store = win.Debugger.store;
+
+ return {
+ actions: win.Debugger.actions,
+ selectors: win.Debugger.selectors,
+ getState: store.getState,
+ store: store,
+ client: win.Debugger.client,
+ toolbox: toolbox,
+ win: win
+ };
+ }
+
+ function waitForState(dbg, predicate) {
+ return new Promise(resolve => {
+ const unsubscribe = dbg.store.subscribe(() => {
+ if (predicate(dbg.store.getState())) {
+ unsubscribe();
+ resolve();
+ }
+ });
+ });
+ }
+
+ function waitForSources(dbg, ...sources) {
+ if (sources.length === 0) {
+ return Promise.resolve();
+ }
+
+ info("Waiting on sources: " + sources.join(", "));
+ const { selectors: { getSources }, store } = dbg;
+ return Promise.all(sources.map(url => {
+ function sourceExists(state) {
+ return getSources(state).some(s => {
+ let sourceUrl = s.get("url");
+ if (!sourceUrl) {
+ return false;
+ }
+ return sourceUrl.includes(url);
+ });
+ }
+
+ if (!sourceExists(store.getState())) {
+ return waitForState(dbg, sourceExists);
+ }
+ }));
+ }
+
+ const selectors = {
+ callStackHeader: ".call-stack-pane ._header",
+ callStackBody: ".call-stack-pane .pane",
+ scopesHeader: ".scopes-pane ._header",
+ breakpointItem: i => `.breakpoints-list .breakpoint:nth-child(${i})`,
+ scopeNode: i => `.scopes-list .tree-node:nth-child(${i}) .object-label`,
+ frame: i => `.frames ul li:nth-child(${i})`,
+ frames: ".frames ul li",
+ gutter: i => `.CodeMirror-code *:nth-child(${i}) .CodeMirror-linenumber`,
+ menuitem: i => `menupopup menuitem:nth-child(${i})`,
+ pauseOnExceptions: ".pause-exceptions",
+ breakpoint: ".CodeMirror-code > .new-breakpoint",
+ highlightLine: ".CodeMirror-code > .highlight-line",
+ codeMirror: ".CodeMirror",
+ resume: ".resume.active",
+ stepOver: ".stepOver.active",
+ stepOut: ".stepOut.active",
+ stepIn: ".stepIn.active",
+ toggleBreakpoints: ".breakpoints-toggle",
+ prettyPrintButton: ".prettyPrint",
+ sourceFooter: ".source-footer",
+ sourceNode: i => `.sources-list .tree-node:nth-child(${i})`,
+ sourceNodes: ".sources-list .tree-node",
+ sourceArrow: i => `.sources-list .tree-node:nth-child(${i}) .arrow`,
+ };
+
+ function getSelector(elementName, ...args) {
+ let selector = selectors[elementName];
+ if (!selector) {
+ throw new Error(`The selector ${elementName} is not defined`);
+ }
+
+ if (typeof selector == "function") {
+ selector = selector(...args);
+ }
+
+ return selector;
+ }
+
+ function findElement(dbg, elementName, ...args) {
+ const selector = getSelector(elementName, ...args);
+ return findElementWithSelector(dbg, selector);
+ }
+
+ function findElementWithSelector(dbg, selector) {
+ return dbg.win.document.querySelector(selector);
+ }
+
+ function findAllElements(dbg, elementName, ...args) {
+ const selector = getSelector(elementName, ...args);
+ return dbg.win.document.querySelectorAll(selector);
+ }
+
+ function clickElement(dbg, elementName, ...args) {
+ const selector = getSelector(elementName, ...args);
+ findElementWithSelector(dbg, selector).click();
+ }
+
+ function waitUntil(predicate, interval = 10) {
+ if (predicate()) {
+ return Promise.resolve(true);
+ }
+ return new Promise(resolve => {
+ window.setTimeout(function () {
+ waitUntil(predicate, interval).then(() => resolve(true));
+ }, interval);
+ });
+ }
+
+ function* waitForSourceCount(dbg, i) {
+ // We are forced to wait until the DOM nodes appear because the
+ // source tree batches its rendering.
+ yield waitUntil(() => {
+ return findAllElements(dbg, "sourceNodes").length >= i;
+ });
+ }
+
Task.spawn(function* () {
- dump("Waiting for debugger load\n");
- let panel = yield toolbox.selectTool("jsdebugger");
- let window = panel.panelWin;
- let document = window.document;
+ Services.prefs.clearUserPref("devtools.debugger.tabs")
+ Services.prefs.clearUserPref("devtools.debugger.pending-selected-location")
+
+ info("Waiting for debugger load");
+ yield toolbox.selectTool("jsdebugger");
+ let dbg = createDebuggerContext(toolbox);
+ window = dbg.win;
+ document = window.document;
- yield window.once(window.EVENTS.SOURCE_SHOWN);
+ yield waitForSources(dbg, testUrl);
+ yield waitForSourceCount(dbg, 6);
- dump("Loaded, selecting the test script to debug\n");
+ info("Loaded, selecting the test script to debug");
+ let mozillaNode = [...document.querySelectorAll(".tree-node")].find(node => {
+ return node.textContent == "mozilla.org";
+ });
+ mozillaNode.click();
+ // clickElement(dbg, "sourceArrow", 5);
+ yield new Promise(() => { })
+
let item = document.querySelector(`.dbg-source-item[tooltiptext="${testUrl}"]`);
let onSourceShown = window.once(window.EVENTS.SOURCE_SHOWN);
item.click();
yield onSourceShown;
- dump("Selected, setting a breakpoint\n");
+ info("Selected, setting a breakpoint");
let { Sources, editor } = window.DebuggerView;
let onBreak = window.once(window.EVENTS.FETCHED_SCOPES);
editor.emit("gutterClick", 1);
yield onBreak;
- dump("Paused, asserting breakpoint position\n");
+ info("Paused, asserting breakpoint position");
let url = Sources.selectedItem.attachment.source.url;
if (url != testUrl) {
throw new Error("Breaking on unexpected script: " + url);
}
let cursor = editor.getCursor();
if (cursor.line != 1) {
throw new Error("Breaking on unexpected line: " + cursor.line);
}
- dump("Now, stepping over\n");
+ info("Now, stepping over");
let stepOver = window.document.querySelector("#step-over");
let onFetchedScopes = window.once(window.EVENTS.FETCHED_SCOPES);
stepOver.click();
yield onFetchedScopes;
- dump("Stepped, asserting step position\n");
+ info("Stepped, asserting step position");
url = Sources.selectedItem.attachment.source.url;
if (url != testUrl) {
throw new Error("Stepping on unexpected script: " + url);
}
cursor = editor.getCursor();
if (cursor.line != 2) {
throw new Error("Stepping on unexpected line: " + cursor.line);
}
- dump("Resume script execution\n");
+ info("Resume script execution");
let resume = window.document.querySelector("#resume");
let onResume = toolbox.target.once("thread-resumed");
resume.click();
yield onResume;
- dump("Close the browser toolbox\n");
+ info("Close the browser toolbox");
toolbox.destroy();
}).catch(error => {
- dump("Error while running code in the browser toolbox process:\n");
- dump(error + "\n");
- dump("stack:\n" + error.stack + "\n");
+ info("Error while running code in the browser toolbox process:");
+ info(error);
+ info("stack:\n" + error.stack);
});
};
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "new " + testScript);
registerCleanupFunction(() => {
env.set("MOZ_TOOLBOX_TEST_SCRIPT", "");
});
let { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -152,26 +152,26 @@ function bindToolboxHandlers() {
// Once the debugger panel opens listen for thread pause / resume.
gToolbox.getPanelWhenReady("jsdebugger").then(panel => {
setupThreadListeners(panel);
});
#endif
}
function setupThreadListeners(panel) {
- updateBadgeText(panel._controller.activeThread.state == "paused");
+ updateBadgeText(panel._selectors().getPause(panel._getState()));
let onPaused = updateBadgeText.bind(null, true);
let onResumed = updateBadgeText.bind(null, false);
- panel.target.on("thread-paused", onPaused);
- panel.target.on("thread-resumed", onResumed);
+ gToolbox.target.on("thread-paused", onPaused);
+ gToolbox.target.on("thread-resumed", onResumed);
panel.once("destroyed", () => {
- panel.off("thread-paused", onPaused);
- panel.off("thread-resumed", onResumed);
+ gToolbox.target.off("thread-paused", onPaused);
+ gToolbox.target.off("thread-resumed", onResumed);
});
}
function updateBadgeText(paused) {
let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"].getService(Ci.nsIMacDockSupport);
dockSupport.badgeText = paused ? "▐▐ " : " ▶";
}