Bug 1346902 - WIP: Update BT tests and integration for new debugger draft
authorJ. Ryan Stinnett <jryans@gmail.com>
Thu, 06 Apr 2017 17:49:35 -0500
changeset 557484 53285f9c99a4b904662ac3ffa4c7ac8fbe303528
parent 557483 ae7cb1a87c7d48e9cf2471a70c96df1ccbd5dccb
child 623080 8c238452e31880e299be5874075b89b47723b0f0
push id52746
push userbmo:jryans@gmail.com
push dateThu, 06 Apr 2017 22:50:01 +0000
bugs1346902
milestone55.0a1
Bug 1346902 - WIP: Update BT tests and integration for new debugger MozReview-Commit-ID: CCKb9zy8WxL
devtools/client/framework/test/browser_browser_toolbox_debugger.js
devtools/client/framework/toolbox-process-window.js
--- 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 ? "▐▐ " : " ▶";
 }