Fix breaking on the "load" event in the debugger (bug 1054159). r=ochameau
authorPanos Astithas <past@mozilla.com>
Fri, 29 Aug 2014 20:51:24 +0300
changeset 235168 337e3aa22117505a66e42628d7eb0f43a8e1e7fa
parent 235054 a9f983fdb8a45352fe1e9f9dbee6395a8cca3f5d
child 235169 358d0174fee4da8eeeb3d5e75c6ba3e75545e3e2
push id57353
push userkwierso@gmail.com
push dateMon, 23 Mar 2015 23:51:33 +0000
treeherdermozilla-inbound@7f5abc27fd53 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1054159
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Fix breaking on the "load" event in the debugger (bug 1054159). r=ochameau
browser/devtools/debugger/test/browser.ini
browser/devtools/debugger/test/browser_dbg_break-on-dom-event-03.js
browser/devtools/debugger/test/doc_event-listeners-04.html
toolkit/devtools/server/actors/script.js
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -54,16 +54,17 @@ support-files =
   doc_conditional-breakpoints.html
   doc_domnode-variables.html
   doc_editor-mode.html
   doc_empty-tab-01.html
   doc_empty-tab-02.html
   doc_event-listeners-01.html
   doc_event-listeners-02.html
   doc_event-listeners-03.html
+  doc_event-listeners-04.html
   doc_frame-parameters.html
   doc_function-display-name.html
   doc_function-search.html
   doc_global-method-override.html
   doc_iframes.html
   doc_included-script.html
   doc_inline-debugger-statement.html
   doc_inline-script.html
@@ -129,16 +130,18 @@ skip-if = e10s || true # bug 1113935
 [browser_dbg_break-on-dom-05.js]
 [browser_dbg_break-on-dom-06.js]
 [browser_dbg_break-on-dom-07.js]
 [browser_dbg_break-on-dom-08.js]
 [browser_dbg_break-on-dom-event-01.js]
 skip-if = e10s || os == "mac" || e10s # Bug 895426
 [browser_dbg_break-on-dom-event-02.js]
 skip-if = e10s # TODO
+[browser_dbg_break-on-dom-event-03.js]
+skip-if = e10s # TODO
 [browser_dbg_breakpoints-actual-location.js]
 [browser_dbg_breakpoints-actual-location2.js]
 [browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js]
 skip-if = e10s # Bug 1093535
 [browser_dbg_breakpoints-button-01.js]
 [browser_dbg_breakpoints-button-02.js]
 [browser_dbg_breakpoints-contextmenu-add.js]
 [browser_dbg_breakpoints-contextmenu.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_break-on-dom-event-03.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the break-on-dom-events request works for load event listeners.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-04.html";
+
+let gClient, gThreadClient;
+
+function test() {
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init();
+    DebuggerServer.addBrowserActors();
+  }
+
+  let transport = DebuggerServer.connectPipe();
+  gClient = new DebuggerClient(transport);
+  gClient.connect((aType, aTraits) => {
+    is(aType, "browser",
+      "Root actor should identify itself as a browser.");
+
+    addTab(TAB_URL)
+      .then(() => attachThreadActorForUrl(gClient, TAB_URL))
+      .then(aThreadClient => gThreadClient = aThreadClient)
+      .then(pauseDebuggee)
+      .then(testBreakOnLoad)
+      .then(closeConnection)
+      .then(finish)
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
+  });
+}
+
+function pauseDebuggee() {
+  let deferred = promise.defer();
+
+  gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+    is(aPacket.type, "paused",
+      "We should now be paused.");
+    is(aPacket.why.type, "debuggerStatement",
+      "The debugger statement was hit.");
+
+    gThreadClient.resume(deferred.resolve);
+  });
+
+  // Spin the event loop before causing the debuggee to pause, to allow
+  // this function to return first.
+  executeSoon(() => triggerButtonClick());
+
+  return deferred.promise;
+}
+
+// Test pause on a load event.
+function testBreakOnLoad() {
+  let deferred = promise.defer();
+
+  // Test calling pauseOnDOMEvents from a running state.
+  gThreadClient.pauseOnDOMEvents(["load"], (aPacket) => {
+    is(aPacket.error, undefined,
+      "The pause-on-load request completed successfully.");
+    let handlers = ["loadHandler"];
+
+    gClient.addListener("paused", function tester(aEvent, aPacket) {
+      is(aPacket.why.type, "pauseOnDOMEvents",
+        "A hidden breakpoint was hit.");
+
+      is(aPacket.frame.where.line, 15, "Found the load event listener.");
+      gClient.removeListener("paused", tester);
+      deferred.resolve();
+
+      gThreadClient.resume(() => triggerButtonClick(handlers.slice(-1)));
+    });
+
+    getTabActorForUrl(gClient, TAB_URL).then(aGrip => {
+      gClient.attachTab(aGrip.actor, (aResponse, aTabClient) => {
+        aTabClient.reload();
+      });
+    });
+  });
+
+  return deferred.promise;
+}
+
+function triggerButtonClick() {
+  let button  = content.document.querySelector("button");
+  EventUtils.sendMouseEvent({ type: "click" }, button);
+}
+
+function closeConnection() {
+  let deferred = promise.defer();
+  gClient.close(deferred.resolve);
+  return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+  gClient = null;
+  gThreadClient = null;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_event-listeners-04.html
@@ -0,0 +1,23 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Debugger test page</title>
+  </head>
+
+  <body>
+    <button>Click me!</button>
+
+    <script type="text/javascript">
+      window.addEventListener("load", function onload() {
+        var button = document.querySelector("button");
+        button.onclick = function () {
+          debugger;
+        };
+      });
+    </script>
+  </body>
+
+</html>
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -24,16 +24,17 @@ const { defer, resolve, reject, all } = 
 loader.lazyGetter(this, "Debugger", () => {
   let Debugger = require("Debugger");
   hackDebugger(Debugger);
   return Debugger;
 });
 loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
 loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
 loader.lazyRequireGetter(this, "CssLogic", "devtools/styleinspector/css-logic", true);
+loader.lazyRequireGetter(this, "events", "sdk/event/core");
 
 let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
       "Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array",
       "Float64Array"];
 
 // Number of items to preview in objects, arrays, maps, sets, lists,
 // collections, etc.
 let OBJECT_PREVIEW_MAX_ITEMS = 10;
@@ -439,16 +440,17 @@ function ThreadActor(aParent, aGlobal)
   this._frameActors = [];
   this._parent = aParent;
   this._dbg = null;
   this._gripDepth = 0;
   this._threadLifetimePool = null;
   this._tabClosed = false;
   this._scripts = null;
   this._sources = null;
+  this._pauseOnDOMEvents = null;
 
   this._options = {
     useSourceMaps: false,
     autoBlackBox: false
   };
 
   this.breakpointActorMap = new BreakpointActorMap;
   this.sourceActorStore = new SourceActorStore;
@@ -462,16 +464,18 @@ function ThreadActor(aParent, aGlobal)
   this.global = aGlobal;
 
   this._allEventsListener = this._allEventsListener.bind(this);
   this.onNewGlobal = this.onNewGlobal.bind(this);
   this.onNewSource = this.onNewSource.bind(this);
   this.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
   this.onDebuggerStatement = this.onDebuggerStatement.bind(this);
   this.onNewScript = this.onNewScript.bind(this);
+  this._onWindowReady = this._onWindowReady.bind(this);
+  events.on(this._parent, "window-ready", this._onWindowReady);
   // Set a wrappedJSObject property so |this| can be sent via the observer svc
   // for the xpcshell harness.
   this.wrappedJSObject = this;
 }
 
 ThreadActor.prototype = {
   // Used by the ObjectActor to keep track of the depth of grip() calls.
   _gripDepth: null,
@@ -615,16 +619,17 @@ ThreadActor.prototype = {
       this.onResume();
     }
 
     // Blow away our source actor ID store because those IDs are only
     // valid for this connection. This is ok because we never keep
     // things like breakpoints across connections.
     this._sourceActorStore = null;
 
+    events.off(this._parent, "window-ready", this._onWindowReady);
     this.clearDebuggees();
     this.conn.removeActorPool(this._threadLifetimePool);
     this._threadLifetimePool = null;
 
     if (this._prettyPrintWorker) {
       this._prettyPrintWorker.removeEventListener(
         "error", this._onPrettyPrintError, false);
       this._prettyPrintWorker.removeEventListener(
@@ -1005,16 +1010,26 @@ ThreadActor.prototype = {
       this._pauseOnDOMEvents = events;
       let els = Cc["@mozilla.org/eventlistenerservice;1"]
                 .getService(Ci.nsIEventListenerService);
       els.addListenerForAllEvents(this.global, this._allEventsListener, true);
     }
   },
 
   /**
+   * If we are tasked with breaking on the load event, we have to add the
+   * listener early enough.
+   */
+  _onWindowReady: function () {
+    this._maybeListenToEvents({
+      pauseOnDOMEvents: this._pauseOnDOMEvents
+    });
+  },
+
+  /**
    * Handle a protocol request to resume execution of the debuggee.
    */
   onResume: function (aRequest) {
     if (this._state !== "paused") {
       return {
         error: "wrongState",
         message: "Can't resume when debuggee isn't paused. Current state is '"
           + this._state + "'"