Properly deal with event handlers that the debugger cannot unwrap (bug 1041266). r=vporof
authorPanos Astithas <past@mozilla.com>
Mon, 21 Jul 2014 18:31:18 +0300
changeset 195363 7ff31c7b215e102c61340eb3af71dadc002afab5
parent 195362 cebc0f24e83973a8232422e6c8d6d74a41a55acc
child 195364 79bd536c7ed2f6c8c402e0098de7f852019bea83
push id46575
push userkwierso@gmail.com
push dateTue, 22 Jul 2014 00:35:21 +0000
treeherdermozilla-inbound@fee5c4bdd713 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvporof
bugs1041266
milestone33.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
Properly deal with event handlers that the debugger cannot unwrap (bug 1041266). r=vporof
browser/devtools/debugger/test/browser.ini
browser/devtools/debugger/test/browser_dbg_event-listeners-03.js
browser/devtools/debugger/test/doc_native-event-handler.html
toolkit/devtools/server/actors/script.js
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -60,16 +60,17 @@ support-files =
   doc_global-method-override.html
   doc_iframes.html
   doc_included-script.html
   doc_inline-debugger-statement.html
   doc_inline-script.html
   doc_large-array-buffer.html
   doc_minified.html
   doc_minified_bogus_map.html
+  doc_native-event-handler.html
   doc_no-page-sources.html
   doc_pause-exceptions.html
   doc_pretty-print.html
   doc_pretty-print-2.html
   doc_pretty-print-3.html
   doc_pretty-print-on-paused.html
   doc_random-javascript.html
   doc_recursion-stack.html
@@ -150,16 +151,17 @@ skip-if = true # Bug 933950 (leaky test)
 [browser_dbg_server-conditional-bp-04.js]
 [browser_dbg_controller-evaluate-01.js]
 [browser_dbg_controller-evaluate-02.js]
 [browser_dbg_debugger-statement.js]
 [browser_dbg_editor-contextmenu.js]
 [browser_dbg_editor-mode.js]
 [browser_dbg_event-listeners-01.js]
 [browser_dbg_event-listeners-02.js]
+[browser_dbg_event-listeners-03.js]
 [browser_dbg_file-reload.js]
 [browser_dbg_function-display-name.js]
 [browser_dbg_global-method-override.js]
 [browser_dbg_globalactor.js]
 [browser_dbg_host-layout.js]
 [browser_dbg_iframes.js]
 [browser_dbg_instruments-pane-collapse.js]
 [browser_dbg_interrupts.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_event-listeners-03.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the eventListeners request works when there are event handlers
+ * that the debugger cannot unwrap.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_native-event-handler.html";
+
+let gClient;
+
+function test() {
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init(() => true);
+    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(pauseDebuggee)
+      .then(testEventListeners)
+      .then(closeConnection)
+      .then(finish)
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
+  });
+}
+
+function pauseDebuggee(aThreadClient) {
+  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.");
+
+    deferred.resolve(aThreadClient);
+  });
+
+  // Spin the event loop before causing the debuggee to pause, to allow
+  // this function to return first.
+  executeSoon(() => {
+    EventUtils.sendMouseEvent({ type: "click" },
+      content.document.querySelector("button"),
+      content);
+  });
+
+  return deferred.promise;
+}
+
+function testEventListeners(aThreadClient) {
+  let deferred = promise.defer();
+
+  aThreadClient.eventListeners(aPacket => {
+    if (aPacket.error) {
+      let msg = "Error getting event listeners: " + aPacket.message;
+      ok(false, msg);
+      deferred.reject(msg);
+      return;
+    }
+
+    // There are 4 event listeners in the page: button.onclick, window.onload
+    // and two more from the video element controls.
+    is(aPacket.listeners.length, 4, "Found all event listeners.");
+    aThreadClient.resume(deferred.resolve);
+  });
+
+  return deferred.promise;
+}
+
+function closeConnection() {
+  let deferred = promise.defer();
+  gClient.close(deferred.resolve);
+  return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+  removeTab(gBrowser.selectedTab);
+  gClient = null;
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_native-event-handler.html
@@ -0,0 +1,22 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>A video element with native event handlers</title>
+    <script type="text/javascript">
+      function initialSetup(event) {
+        debugger;
+      }
+
+      window.addEventListener("load", function() {}, false);
+    </script>
+  </head>
+  <body>
+    <button onclick="initialSetup()">Click me!</button>
+    <!-- the "controls" attribute ensures that there are extra event handlers in
+         the element. -->
+    <video controls></video>
+  </body>
+</html>
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -1856,16 +1856,21 @@ ThreadActor.prototype = {
         }
         if (!!node[handlerName]) {
           listenerForm.isEventHandler = !!node[handlerName];
         }
         // Get the Debugger.Object for the listener object.
         let listenerDO = this.globalDebugObject.makeDebuggeeValue(listener);
         // If the listener is an object with a 'handleEvent' method, use that.
         if (listenerDO.class == "Object" || listenerDO.class == "XULElement") {
+          // For some events we don't have permission to access the
+          // 'handleEvent' property when running in content scope.
+          if (!listenerDO.unwrap()) {
+            continue;
+          }
           let heDesc;
           while (!heDesc && listenerDO) {
             heDesc = listenerDO.getOwnPropertyDescriptor("handleEvent");
             listenerDO = listenerDO.proto;
           }
           if (heDesc && heDesc.value) {
             listenerDO = heDesc.value;
           }