Bug 1315639 - Event listener popup needs rewriting r?pbro draft
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Fri, 04 Nov 2016 17:16:50 +0000
changeset 445209 974836aaa3c790516c3c25cb9882ad15a97d285f
parent 445174 15b774db7eab7fc4c9489db9c9f77a2e73536e22
child 538481 1026f7fbd8a3b3848e702720c4b28146a075be72
push id37471
push userbmo:mratcliffe@mozilla.com
push dateTue, 29 Nov 2016 10:28:09 +0000
reviewerspbro
bugs1315639
milestone53.0a1
Bug 1315639 - Event listener popup needs rewriting r?pbro Changes: - Removed 5 lines from toolkit/content/license.html as requested by gerv. MozReview-Commit-ID: COFNoCDVyp6
devtools/client/inspector/markup/test/browser.ini
devtools/client/inspector/markup/test/browser_markup_events1.js
devtools/client/inspector/markup/test/browser_markup_events2.js
devtools/client/inspector/markup/test/browser_markup_events3.js
devtools/client/inspector/markup/test/browser_markup_events_01.js
devtools/client/inspector/markup/test/browser_markup_events_02.js
devtools/client/inspector/markup/test/browser_markup_events_03.js
devtools/client/inspector/markup/test/browser_markup_events_04.js
devtools/client/inspector/markup/test/browser_markup_events_jquery_1.0.js
devtools/client/inspector/markup/test/browser_markup_events_jquery_1.1.js
devtools/client/inspector/markup/test/browser_markup_events_jquery_1.11.1.js
devtools/client/inspector/markup/test/browser_markup_events_jquery_1.2.js
devtools/client/inspector/markup/test/browser_markup_events_jquery_1.3.js
devtools/client/inspector/markup/test/browser_markup_events_jquery_1.4.js
devtools/client/inspector/markup/test/browser_markup_events_jquery_1.6.js
devtools/client/inspector/markup/test/browser_markup_events_jquery_1.7.js
devtools/client/inspector/markup/test/browser_markup_events_jquery_2.1.1.js
devtools/client/inspector/markup/test/doc_markup_events1.html
devtools/client/inspector/markup/test/doc_markup_events2.html
devtools/client/inspector/markup/test/doc_markup_events3.html
devtools/client/inspector/markup/test/doc_markup_events_01.html
devtools/client/inspector/markup/test/doc_markup_events_02.html
devtools/client/inspector/markup/test/doc_markup_events_03.html
devtools/client/inspector/markup/test/doc_markup_events_04.html
devtools/client/inspector/markup/test/helper_diff.js
devtools/client/inspector/markup/test/helper_events_test_runner.js
devtools/client/inspector/markup/views/markup-container.js
devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
devtools/client/themes/tooltips.css
devtools/server/actors/inspector.js
devtools/server/event-parsers.js
toolkit/content/license.html
--- a/devtools/client/inspector/markup/test/browser.ini
+++ b/devtools/client/inspector/markup/test/browser.ini
@@ -3,19 +3,20 @@ tags = devtools
 subsuite = devtools
 support-files =
   actor_events_form.js
   doc_markup_anonymous.html
   doc_markup_dragdrop.html
   doc_markup_dragdrop_autoscroll_01.html
   doc_markup_dragdrop_autoscroll_02.html
   doc_markup_edit.html
-  doc_markup_events1.html
-  doc_markup_events2.html
-  doc_markup_events3.html
+  doc_markup_events_01.html
+  doc_markup_events_02.html
+  doc_markup_events_03.html
+  doc_markup_events_04.html
   doc_markup_events_form.html
   doc_markup_events_jquery.html
   doc_markup_events-overflow.html
   doc_markup_flashing.html
   doc_markup_html_mixed_case.html
   doc_markup_image_and_canvas.html
   doc_markup_image_and_canvas_2.html
   doc_markup_links.html
@@ -29,16 +30,17 @@ support-files =
   doc_markup_toggle.html
   doc_markup_tooltip.png
   doc_markup_void_elements.html
   doc_markup_void_elements.xhtml
   doc_markup_whitespace.html
   doc_markup_xul.xul
   head.js
   helper_attributes_test_runner.js
+  helper_diff.js
   helper_events_test_runner.js
   helper_markup_accessibility_navigation.js
   helper_outerhtml_test_runner.js
   helper_style_attr_test_runner.js
   lib_jquery_1.0.js
   lib_jquery_1.1.js
   lib_jquery_1.2_min.js
   lib_jquery_1.3_min.js
@@ -75,19 +77,20 @@ subsuite = clipboard
 [browser_markup_dragdrop_autoscroll_02.js]
 [browser_markup_dragdrop_distance.js]
 [browser_markup_dragdrop_draggable.js]
 [browser_markup_dragdrop_dragRootNode.js]
 [browser_markup_dragdrop_escapeKeyPress.js]
 [browser_markup_dragdrop_invalidNodes.js]
 [browser_markup_dragdrop_reorder.js]
 [browser_markup_dragdrop_tooltip.js]
-[browser_markup_events1.js]
-[browser_markup_events2.js]
-[browser_markup_events3.js]
+[browser_markup_events_01.js]
+[browser_markup_events_02.js]
+[browser_markup_events_03.js]
+[browser_markup_events_04.js]
 [browser_markup_events_form.js]
 [browser_markup_events_jquery_1.0.js]
 [browser_markup_events_jquery_1.1.js]
 [browser_markup_events_jquery_1.2.js]
 [browser_markup_events_jquery_1.3.js]
 [browser_markup_events_jquery_1.4.js]
 [browser_markup_events_jquery_1.6.js]
 [browser_markup_events_jquery_1.7.js]
rename from devtools/client/inspector/markup/test/browser_markup_events1.js
rename to devtools/client/inspector/markup/test/browser_markup_events_01.js
--- a/devtools/client/inspector/markup/test/browser_markup_events1.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_01.js
@@ -3,32 +3,34 @@
  http://creativecommons.org/publicdomain/zero/1.0/ */
 /* import-globals-from helper_events_test_runner.js */
 
 "use strict";
 
 // Test that markup view event bubbles show the correct event info for DOM
 // events.
 
-const TEST_URL = URL_ROOT + "doc_markup_events1.html";
+const TEST_URL = URL_ROOT + "doc_markup_events_01.html";
 
 loadHelperScript("helper_events_test_runner.js");
 
 const TEST_DATA = [ // eslint-disable-line
   {
     selector: "html",
     expected: [
       {
         type: "load",
         filename: TEST_URL,
         attributes: [
           "Bubbling",
           "DOM0"
         ],
-        handler: "init();"
+        handler: "function onload(event) {\n" +
+                 "  init();\n" +
+                 "}"
       }
     ]
   },
   {
     selector: "#container",
     expected: [
       {
         type: "mouseover",
@@ -117,31 +119,33 @@ const TEST_DATA = [ // eslint-disable-li
     expected: [
       {
         type: "click",
         filename: TEST_URL,
         attributes: [
           "Bubbling",
           "DOM0"
         ],
-        handler: "alert('DOM0')"
+        handler: "function onclick(event) {\n" +
+                 "  alert('DOM0')\n" +
+                 "}"
       }
     ]
   },
   {
     selector: "#handleevent",
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":67",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "handleEvent: function(blah) {\n" +
+        handler: "function(blah) {\n" +
                  "  alert(\"handleEvent\");\n" +
                  "}"
       }
     ]
   }
 ];
 
 add_task(function* () {
rename from devtools/client/inspector/markup/test/browser_markup_events2.js
rename to devtools/client/inspector/markup/test/browser_markup_events_02.js
--- a/devtools/client/inspector/markup/test/browser_markup_events2.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_02.js
@@ -3,17 +3,17 @@
  http://creativecommons.org/publicdomain/zero/1.0/ */
 /* import-globals-from helper_events_test_runner.js */
 
 "use strict";
 
 // Test that markup view event bubbles show the correct event info for DOM
 // events.
 
-const TEST_URL = URL_ROOT + "doc_markup_events2.html";
+const TEST_URL = URL_ROOT + "doc_markup_events_02.html";
 
 loadHelperScript("helper_events_test_runner.js");
 
 const TEST_DATA = [ // eslint-disable-line
   {
     selector: "#fatarrow",
     expected: [
       {
@@ -65,33 +65,33 @@ const TEST_DATA = [ // eslint-disable-li
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":62",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "function boundClickHandler(event) {\n" +
+        handler: "function(event) {\n" +
                  "  alert(\"Bound event\");\n" +
                  "}"
       }
     ]
   },
   {
     selector: "#boundhe",
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":85",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "handleEvent: function() {\n" +
+        handler: "function() {\n" +
                  "  alert(\"boundHandleEvent\");\n" +
                  "}"
       }
     ]
   },
   {
     selector: "#comment-inline",
     expected: [
@@ -129,33 +129,33 @@ const TEST_DATA = [ // eslint-disable-li
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":71",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "anonObjectMethod: function() {\n" +
+        handler: "function() {\n" +
                  "  alert(\"obj.anonObjectMethod\");\n" +
                  "}"
       }
     ]
   },
   {
     selector: "#object-method",
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":75",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "objectMethod: function kay() {\n" +
+        handler: "function kay() {\n" +
                  "  alert(\"obj.objectMethod\");\n" +
                  "}"
       }
     ]
   }
 ];
 
 add_task(function* () {
rename from devtools/client/inspector/markup/test/browser_markup_events3.js
rename to devtools/client/inspector/markup/test/browser_markup_events_03.js
--- a/devtools/client/inspector/markup/test/browser_markup_events3.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_03.js
@@ -3,159 +3,114 @@
  http://creativecommons.org/publicdomain/zero/1.0/ */
 /* import-globals-from helper_events_test_runner.js */
 
 "use strict";
 
 // Test that markup view event bubbles show the correct event info for DOM
 // events.
 
-const TEST_URL = URL_ROOT + "doc_markup_events3.html";
+const TEST_URL = URL_ROOT + "doc_markup_events_03.html";
 
 loadHelperScript("helper_events_test_runner.js");
 
 const TEST_DATA = [ // eslint-disable-line
   {
     selector: "#es6-method",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":91",
+        filename: TEST_URL + ":66",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "es6Method() {\n" +
+        handler: "function es6Method(foo, bar) {\n" +
                  "  alert(\"obj.es6Method\");\n" +
                  "}"
       }
     ]
   },
   {
     selector: "#generator",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":96",
+        filename: TEST_URL + ":85",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: "function* generator() {\n" +
                  "  alert(\"generator\");\n" +
                  "}"
       }
     ]
   },
   {
     selector: "#anon-generator",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":55",
+        filename: TEST_URL + ":43",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: "function*() {\n" +
                  "  alert(\"anonGenerator\");\n" +
                  "}"
       }
     ]
   },
   {
     selector: "#named-function-expression",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":23",
+        filename: TEST_URL + ":20",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "let namedFunctionExpression =\n" +
-                 "  function foo() {\n" +
-                 "    alert(\"namedFunctionExpression\");\n" +
-                 "  }"
+        handler: "function foo() {\n" +
+                 "  alert(\"namedFunctionExpression\");\n" +
+                 "}"
       }
     ]
   },
   {
     selector: "#anon-function-expression",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":27",
+        filename: TEST_URL + ":24",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "let anonFunctionExpression = function() {\n" +
+        handler: "function() {\n" +
                  "  alert(\"anonFunctionExpression\");\n" +
                  "}"
       }
     ]
   },
   {
     selector: "#returned-function",
     expected: [
       {
         type: "click",
-        filename: TEST_URL + ":32",
+        filename: TEST_URL + ":29",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: "function bar() {\n" +
                  "  alert(\"returnedFunction\");\n" +
                  "}"
       }
     ]
   },
-  {
-    selector: "#constructed-function",
-    expected: [
-      {
-        type: "click",
-        filename: TEST_URL + ":0",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: ""
-      }
-    ]
-  },
-  {
-    selector: "#constructed-function-with-body-string",
-    expected: [
-      {
-        type: "click",
-        filename: TEST_URL + ":0",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "alert(\"constructedFuncWithBodyString\");"
-      }
-    ]
-  },
-  {
-    selector: "#multiple-assignment",
-    expected: [
-      {
-        type: "click",
-        filename: TEST_URL + ":42",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "let multipleAssignment = foo = bar = function multi() {\n" +
-                 "  alert(\"multipleAssignment\");\n" +
-                 "}"
-      }
-    ]
-  },
 ];
 
 add_task(function* () {
   yield runEventPopupTests(TEST_URL, TEST_DATA);
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/browser_markup_events_04.js
@@ -0,0 +1,157 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* import-globals-from helper_events_test_runner.js */
+
+"use strict";
+
+// Test that markup view event bubbles show the correct event info for DOM
+// events.
+
+const TEST_URL = URL_ROOT + "doc_markup_events_04.html";
+
+loadHelperScript("helper_events_test_runner.js");
+
+const TEST_DATA = [ // eslint-disable-line
+  {
+    selector: "html",
+    expected: [
+      {
+        type: "load",
+        filename: TEST_URL,
+        attributes: [
+          "Bubbling",
+          "DOM0"
+        ],
+        handler: "function onload(event) {\n" +
+                 "  init();\n" +
+                 "}"
+      },
+      {
+        type: "click",
+        filename: TEST_URL + ":56",
+        attributes: [
+          "Bubbling",
+          "DOM2"
+        ],
+        handler: "function(foo2, bar2) {\n" +
+                 "  alert(\"documentElement event listener clicked\");\n" +
+                 "}"
+      },
+      {
+        type: "click",
+        filename: TEST_URL + ":52",
+        attributes: [
+          "Bubbling",
+          "DOM2"
+        ],
+        handler: "function(foo, bar) {\n" +
+                 "  alert(\"document event listener clicked\");\n" +
+                 "}"
+      },
+    ]
+  },
+  {
+    selector: "#constructed-function",
+    expected: [
+      {
+        type: "click",
+        filename: TEST_URL,
+        attributes: [
+          "Bubbling",
+          "DOM2"
+        ],
+        handler: "function anonymous() {\n" +
+                 "\n" +
+                 "}"
+      }
+    ]
+  },
+  {
+    selector: "#constructed-function-with-body-string",
+    expected: [
+      {
+        type: "click",
+        filename: TEST_URL,
+        attributes: [
+          "Bubbling",
+          "DOM2"
+        ],
+        handler: "function anonymous(a, b, c) {\n" +
+                 "  alert(\"constructedFuncWithBodyString\");\n" +
+        "}"
+      }
+    ]
+  },
+  {
+    selector: "#multiple-assignment",
+    expected: [
+      {
+        type: "click",
+        filename: TEST_URL + ":24",
+        attributes: [
+          "Bubbling",
+          "DOM2"
+        ],
+        handler: "function multi() {\n" +
+                 "  alert(\"multipleAssignment\");\n" +
+                 "}"
+      }
+    ]
+  },
+  {
+    selector: "#promise",
+    expected: [
+      {
+        type: "click",
+        filename: "[native code]",
+        attributes: [
+          "Bubbling",
+          "DOM2"
+        ],
+        handler: "function() {\n" +
+                 "  [native code]\n" +
+                 "}"
+      }
+    ]
+  },
+  {
+    selector: "#arraysort",
+    expected: [
+      {
+        type: "click",
+        filename: "[native code]",
+        attributes: [
+          "Bubbling",
+          "DOM2"
+        ],
+        handler: "function sort(arr, comparefn) {\n" +
+                 "  [native code]\n" +
+                 "}"
+      }
+    ]
+  },
+  {
+    selector: "#handleEvent",
+    expected: [
+      {
+        type: "click",
+        filename: TEST_URL + ":77",
+        attributes: [
+          "Bubbling",
+          "DOM2"
+        ],
+        handler: "function(event) {\n" +
+                 "  switch (event.type) {\n" +
+                 "    case \"click\":\n" +
+                 "      alert(\"handleEvent click\");\n" +
+                 "  }\n" +
+                 "}"
+      }
+    ]
+  },
+];
+
+add_task(function* () {
+  yield runEventPopupTests(TEST_URL, TEST_DATA);
+});
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.0.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.0.js
@@ -14,21 +14,21 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "html",
     expected: [
       {
         type: "load",
-        filename: URL_ROOT + TEST_LIB,
+        filename: URL_ROOT + TEST_LIB + ":1117",
         attributes: [
           "jQuery"
         ],
-        handler: "ready: function() {\n" +
+        handler: "function() {\n" +
                  "  // Make sure that the DOM is not already loaded\n" +
                  "  if (!jQuery.isReady) {\n" +
                  "    // Remember that the DOM is ready\n" +
                  "    jQuery.isReady = true;\n" +
                  "\n" +
                  "    // If there are functions bound, to execute\n" +
                  "    if (jQuery.readyList) {\n" +
                  "      // Execute all of them\n" +
@@ -38,20 +38,20 @@ const TEST_DATA = [
                  "      // Reset the list of functions\n" +
                  "      jQuery.readyList = null;\n" +
                  "    }\n" +
                  "  }\n" +
                  "}"
       },
       {
         type: "load",
-        filename: TEST_URL,
+        filename: TEST_URL + ":27",
         attributes: [
           "Bubbling",
-          "DOM0"
+          "DOM2"
         ],
         handler: "() => {\n" +
                  "  var handler1 = function liveDivDblClick() {\n" +
                  "    alert(1);\n" +
                  "  };\n" +
                  "  var handler2 = function liveDivDragStart() {\n" +
                  "    alert(2);\n" +
                  "  };\n" +
@@ -99,22 +99,22 @@ const TEST_DATA = [
                  "  var div = $(\"div\")[0];\n" +
                  "  $(div).click(handler7);\n" +
                  "  $(div).click(handler8);\n" +
                  "  $(div).keydown(handler9);\n" +
                  "}"
       },
       {
         type: "load",
-        filename: URL_ROOT + TEST_LIB,
+        filename: URL_ROOT + TEST_LIB + ":894",
         attributes: [
           "Bubbling",
-          "DOM0"
+          "DOM2"
         ],
-        handler: "handle: function(event) {\n" +
+        handler: "function(event) {\n" +
                  "  if (typeof jQuery == \"undefined\") return;\n" +
                  "\n" +
                  "  event = event || jQuery.event.fix(window.event);\n" +
                  "\n" +
                  "  // If no correct event was found, fail\n" +
                  "  if (!event) return;\n" +
                  "\n" +
                  "  var returnValue = true;\n" +
@@ -126,50 +126,75 @@ const TEST_DATA = [
                  "      event.preventDefault();\n" +
                  "      event.stopPropagation();\n" +
                  "      returnValue = false;\n" +
                  "    }\n" +
                  "  }\n" +
                  "\n" +
                  "  return returnValue;\n" +
                  "}"
+      },
+      {
+        type: "DOMContentLoaded",
+        filename: URL_ROOT + TEST_LIB + ":1117",
+        attributes: [
+          "Bubbling",
+          "DOM2"
+        ],
+        handler: "function() {\n" +
+                 "  // Make sure that the DOM is not already loaded\n" +
+                 "  if (!jQuery.isReady) {\n" +
+                 "    // Remember that the DOM is ready\n" +
+                 "    jQuery.isReady = true;\n" +
+                 "\n" +
+                 "    // If there are functions bound, to execute\n" +
+                 "    if (jQuery.readyList) {\n" +
+                 "      // Execute all of them\n" +
+                 "      for (var i = 0; i < jQuery.readyList.length; i++)\n" +
+                 "        jQuery.readyList[i].apply(document);\n" +
+                 "\n" +
+                 "      // Reset the list of functions\n" +
+                 "      jQuery.readyList = null;\n" +
+                 "    }\n" +
+                 "  }\n" +
+                 "}"
       }
     ]
   },
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":34",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler7 = function divClick1() {\n" +
+        handler: "function divClick1() {\n" +
                  "  alert(7);\n" +
                  "}"
       },
       {
         type: "click",
         filename: TEST_URL + ":35",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler8 = function divClick2() {\n" +
+        handler: "function divClick2() {\n" +
                  "  alert(8);\n" +
                  "}"
       },
       {
         type: "click",
         filename: URL_ROOT + TEST_LIB + ":894",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "handle: function(event) {\n" +
+        handler: "function(event) {\n" +
                  "  if (typeof jQuery == \"undefined\") return;\n" +
                  "\n" +
                  "  event = event || jQuery.event.fix(window.event);\n" +
                  "\n" +
                  "  // If no correct event was found, fail\n" +
                  "  if (!event) return;\n" +
                  "\n" +
                  "  var returnValue = true;\n" +
@@ -188,28 +213,28 @@ const TEST_DATA = [
                  "}"
       },
       {
         type: "keydown",
         filename: TEST_URL + ":36",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler9 = function divKeyDown() {\n" +
+        handler: "function divKeyDown() {\n" +
                  "  alert(9);\n" +
                  "}"
       },
       {
         type: "keydown",
         filename: URL_ROOT + TEST_LIB + ":894",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "handle: function(event) {\n" +
+        handler: "function(event) {\n" +
                  "  if (typeof jQuery == \"undefined\") return;\n" +
                  "\n" +
                  "  event = event || jQuery.event.fix(window.event);\n" +
                  "\n" +
                  "  // If no correct event was found, fail\n" +
                  "  if (!event) return;\n" +
                  "\n" +
                  "  var returnValue = true;\n" +
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.1.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.1.js
@@ -14,21 +14,21 @@ loadHelperScript("helper_events_test_run
 
 /*eslint-disable */
 const TEST_DATA = [
   {
     selector: "html",
     expected: [
       {
         type: "load",
-        filename: URL_ROOT + TEST_LIB,
+        filename: URL_ROOT + TEST_LIB + ":1387",
         attributes: [
           "jQuery"
         ],
-        handler: "ready: function() {\n" +
+        handler: "function() {\n" +
                  "  // Make sure that the DOM is not already loaded\n" +
                  "  if (!jQuery.isReady) {\n" +
                  "    // Remember that the DOM is ready\n" +
                  "    jQuery.isReady = true;\n" +
                  "\n" +
                  "    // If there are functions bound, to execute\n" +
                  "    if (jQuery.readyList) {\n" +
                  "      // Execute all of them\n" +
@@ -42,20 +42,20 @@ const TEST_DATA = [
                  "    // Remove event lisenter to avoid memory leak\n" +
                  "    if (jQuery.browser.mozilla || jQuery.browser.opera)\n" +
                  "      document.removeEventListener(\"DOMContentLoaded\", jQuery.ready, false);\n" +
                  "  }\n" +
                  "}"
       },
       {
         type: "load",
-        filename: TEST_URL,
+        filename: TEST_URL + ":27",
         attributes: [
           "Bubbling",
-          "DOM0"
+          "DOM2"
         ],
         handler: "() => {\n" +
                  "  var handler1 = function liveDivDblClick() {\n" +
                  "    alert(1);\n" +
                  "  };\n" +
                  "  var handler2 = function liveDivDragStart() {\n" +
                  "    alert(2);\n" +
                  "  };\n" +
@@ -103,22 +103,22 @@ const TEST_DATA = [
                  "  var div = $(\"div\")[0];\n" +
                  "  $(div).click(handler7);\n" +
                  "  $(div).click(handler8);\n" +
                  "  $(div).keydown(handler9);\n" +
                  "}"
       },
       {
         type: "load",
-        filename: URL_ROOT + TEST_LIB,
+        filename: URL_ROOT + TEST_LIB + ":1224",
         attributes: [
           "Bubbling",
-          "DOM0"
+          "DOM2"
         ],
-        handler: "handle: function(event) {\n" +
+        handler: "function(event) {\n" +
                  "  if (typeof jQuery == \"undefined\") return false;\n" +
                  "\n" +
                  "  // Empty object is for triggered events with no data\n" +
                  "  event = jQuery.event.fix(event || window.event || {});\n" +
                  "\n" +
                  "  // returned undefined or false\n" +
                  "  var returnValue;\n" +
                  "\n" +
@@ -152,38 +152,38 @@ const TEST_DATA = [
     selector: "#testdiv",
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":34",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler7 = function divClick1() {\n" +
+        handler: "function divClick1() {\n" +
                  "  alert(7);\n" +
                  "}"
       },
       {
         type: "click",
         filename: TEST_URL + ":35",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler8 = function divClick2() {\n" +
+        handler: "function divClick2() {\n" +
                  "  alert(8);\n" +
                  "}"
       },
       {
         type: "click",
         filename: URL_ROOT + TEST_LIB + ":1224",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "handle: function(event) {\n" +
+        handler: "function(event) {\n" +
                  "  if (typeof jQuery == \"undefined\") return false;\n" +
                  "\n" +
                  "  // Empty object is for triggered events with no data\n" +
                  "  event = jQuery.event.fix(event || window.event || {});\n" +
                  "\n" +
                  "  // returned undefined or false\n" +
                  "  var returnValue;\n" +
                  "\n" +
@@ -212,28 +212,28 @@ const TEST_DATA = [
                  "}"
       },
       {
         type: "keydown",
         filename: TEST_URL + ":36",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler9 = function divKeyDown() {\n" +
+        handler: "function divKeyDown() {\n" +
                  "  alert(9);\n" +
                  "}"
       },
       {
         type: "keydown",
         filename: URL_ROOT + TEST_LIB + ":1224",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "handle: function(event) {\n" +
+        handler: "function(event) {\n" +
                  "  if (typeof jQuery == \"undefined\") return false;\n" +
                  "\n" +
                  "  // Empty object is for triggered events with no data\n" +
                  "  event = jQuery.event.fix(event || window.event || {});\n" +
                  "\n" +
                  "  // returned undefined or false\n" +
                  "  var returnValue;\n" +
                  "\n" +
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.11.1.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.11.1.js
@@ -85,109 +85,87 @@ const TEST_DATA = [
     selector: "#testdiv",
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":34",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler7 = function divClick1() {\n" +
+        handler: "function divClick1() {\n" +
                  "  alert(7);\n" +
                  "}"
       },
       {
         type: "click",
         filename: TEST_URL + ":35",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler8 = function divClick2() {\n" +
+        handler: "function divClick2() {\n" +
                  "  alert(8);\n" +
                  "}"
       },
       {
-        type: "click",
-        filename: URL_ROOT + TEST_LIB + ":3",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "k = r.handle = function(a) {\n" +
-                 "  return typeof m === K || a && m.event.triggered === a.type ? void 0 : m.event.dispatch.apply(k.elem, arguments)\n" +
-                 "}"
-      },
-      {
         type: "keydown",
         filename: TEST_URL + ":36",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler9 = function divKeyDown() {\n" +
+        handler: "function divKeyDown() {\n" +
                  "  alert(9);\n" +
                  "}"
-      },
-      {
-        type: "keydown",
-        filename: URL_ROOT + TEST_LIB + ":3",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "k = r.handle = function(a) {\n" +
-                 "  return typeof m === K || a && m.event.triggered === a.type ? void 0 : m.event.dispatch.apply(k.elem, arguments)\n" +
-                 "}"
       }
     ]
   },
 
   {
     selector: "#livediv",
     expected: [
       {
         type: "dragend",
         filename: TEST_URL + ":31",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler4 = function liveDivDragEnd() {\n" +
+        handler: "function liveDivDragEnd() {\n" +
                  "  alert(4);\n" +
                  "}"
       },
       {
         type: "dragleave",
         filename: TEST_URL + ":30",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler3 = function liveDivDragLeave() {\n" +
+        handler: "function liveDivDragLeave() {\n" +
                  "  alert(3);\n" +
                  "}"
       },
       {
         type: "dragover",
         filename: TEST_URL + ":33",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler6 = function liveDivDragOver() {\n" +
+        handler: "function liveDivDragOver() {\n" +
                  "  alert(6);\n" +
                  "}"
       },
       {
         type: "drop",
         filename: TEST_URL + ":32",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler5 = function liveDivDrop() {\n" +
+        handler: "function liveDivDrop() {\n" +
                  "  alert(5);\n" +
                  "}"
       }
     ]
   },
 ];
 /*eslint-enable */
 
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.2.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.2.js
@@ -80,17 +80,17 @@ const TEST_DATA = [
       },
       {
         type: "load",
         filename: URL_ROOT + TEST_LIB,
         attributes: [
           "Bubbling",
           "DOM0"
         ],
-        handler: "handle: function(event) {\n" +
+        handler: "function(event) {\n" +
                  "  if (typeof jQuery == \"undefined\") return false;\n" +
                  "\n" +
                  "  // Empty object is for triggered events with no data\n" +
                  "  event = jQuery.event.fix(event || window.event || {});\n" +
                  "\n" +
                  "  // returned undefined or false\n" +
                  "  var returnValue;\n" +
                  "\n" +
@@ -124,27 +124,27 @@ const TEST_DATA = [
     selector: "#testdiv",
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":34",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler7 = function divClick1() {\n" +
+        handler: "function divClick1() {\n" +
                  "  alert(7);\n" +
                  "}"
       },
       {
         type: "click",
         filename: TEST_URL + ":35",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler8 = function divClick2() {\n" +
+        handler: "function divClick2() {\n" +
                  "  alert(8);\n" +
                  "}"
       },
       {
         type: "click",
         filename: URL_ROOT + TEST_LIB + ":24",
         attributes: [
           "Bubbling",
@@ -158,17 +158,17 @@ const TEST_DATA = [
                  "}"
       },
       {
         type: "keydown",
         filename: TEST_URL + ":36",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler9 = function divKeyDown() {\n" +
+        handler: "function divKeyDown() {\n" +
                  "  alert(9);\n" +
                  "}"
       },
       {
         type: "keydown",
         filename: URL_ROOT + TEST_LIB + ":24",
         attributes: [
           "Bubbling",
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.3.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.3.js
@@ -18,17 +18,17 @@ const TEST_DATA = [
     selector: "html",
     expected: [
       {
         type: "load",
         filename: URL_ROOT + TEST_LIB + ":19",
         attributes: [
           "jQuery"
         ],
-        handler: "ready: function() {\n" +
+        handler: "function() {\n" +
                  "  if (!n.isReady) {\n" +
                  "    n.isReady = true;\n" +
                  "    if (n.readyList) {\n" +
                  "      n.each(n.readyList, function() {\n" +
                  "        this.call(document, n)\n" +
                  "      });\n" +
                  "      n.readyList = null\n" +
                  "    }\n" +
@@ -93,130 +93,176 @@ const TEST_DATA = [
                  "\n" +
                  "  var div = $(\"div\")[0];\n" +
                  "  $(div).click(handler7);\n" +
                  "  $(div).click(handler8);\n" +
                  "  $(div).keydown(handler9);\n" +
                  "}"
       },
       {
-        type: "load",
-        filename: URL_ROOT + TEST_LIB + ":19",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "function() {\n" +
-                 "  return typeof n !== \"undefined\" && !n.event.triggered ? n.event.handle.apply(arguments.callee.elem, arguments) : g\n" +
-                 "}"
-      },
-      {
         type: "unload",
         filename: URL_ROOT + TEST_LIB + ":19",
         attributes: [
           "jQuery"
         ],
         handler: "function(H) {\n" +
                  "  n(this).unbind(H, D);\n" +
                  "  return (E || G).apply(this, arguments)\n" +
                  "}"
       },
       {
-        type: "unload",
+        type: "dblclick",
+        filename: URL_ROOT + TEST_LIB + ":19",
+        attributes: [
+          "jQuery"
+        ],
+        handler: "function c(G) {\n" +
+                 "  var D = RegExp(\"(^|\\\\.)\" + G.type + \"(\\\\.|$)\"),\n" +
+                 "    F = true,\n" +
+                 "    E = [];\n" +
+                 "  n.each(n.data(this, \"events\").live || [], function(H, I) {\n" +
+                 "    if (D.test(I.type)) {\n" +
+                 "      var J = n(G.target).closest(I.data)[0];\n" +
+                 "      if (J) {\n" +
+                 "        E.push({\n" +
+                 "          elem: J,\n" +
+                 "          fn: I\n" +
+                 "        })\n" +
+                 "      }\n" +
+                 "    }\n" +
+                 "  });\n" +
+                 "  n.each(E, function() {\n" +
+                 "    if (!G.isImmediatePropagationStopped() && " + "this.fn.call(this.elem, G, this.fn.data) === false) {\n" +
+                 "      F = false\n" +
+                 "    }\n" +
+                 "  });\n" +
+                 "  return F\n" +
+                 "}"
+      },
+      {
+        type: "DOMContentLoaded",
         filename: URL_ROOT + TEST_LIB + ":19",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
         handler: "function() {\n" +
-                 "  return typeof n !== \"undefined\" && !n.event.triggered ? n.event.handle.apply(arguments.callee.elem, arguments) : g\n" +
+                 "  document.removeEventListener(\"DOMContentLoaded\", arguments.callee, false);\n" +
+                 "  n.ready()\n" +
+                 "}"
+      },
+      {
+        type: "dragstart",
+        filename: URL_ROOT + TEST_LIB + ":19",
+        attributes: [
+          "jQuery"
+        ],
+        handler: "function c(G) {\n" +
+                 "  var D = RegExp(\"(^|\\\\.)\" + G.type + \"(\\\\.|$)\"),\n" +
+                 "    F = true,\n" +
+                 "    E = [];\n" +
+                 "  n.each(n.data(this, \"events\").live || [], function(H, I) {\n" +
+                 "    if (D.test(I.type)) {\n" +
+                 "      var J = n(G.target).closest(I.data)[0];\n" +
+                 "      if (J) {\n" +
+                 "        E.push({\n" +
+                 "          elem: J,\n" +
+                 "          fn: I\n" +
+                 "        })\n" +
+                 "      }\n" +
+                 "    }\n" +
+                 "  });\n" +
+                 "  n.each(E, function() {\n" +
+                 "    if (!G.isImmediatePropagationStopped() && " + "this.fn.call(this.elem, G, this.fn.data) === false) {\n" +
+                 "      F = false\n" +
+                 "    }\n" +
+                 "  });\n" +
+                 "  return F\n" +
+                 "}"
+      },
+      {
+        type: "live",
+        filename: URL_ROOT + TEST_LIB + ":19",
+        attributes: [
+          "jQuery"
+        ],
+        handler: "function() {\n" +
+                 "  return E.apply(this, arguments)\n" +
+                 "}"
+      },
+      {
+        type: "live",
+        filename: URL_ROOT + TEST_LIB + ":19",
+        attributes: [
+          "jQuery"
+        ],
+        handler: "function() {\n" +
+                 "  return E.apply(this, arguments)\n" +
                  "}"
       }
     ]
   },
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":34",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler7 = function divClick1() {\n" +
+        handler: "function divClick1() {\n" +
                  "  alert(7);\n" +
                  "}"
       },
       {
         type: "click",
         filename: TEST_URL + ":35",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler8 = function divClick2() {\n" +
+        handler: "function divClick2() {\n" +
                  "  alert(8);\n" +
                  "}"
       },
       {
-        type: "click",
-        filename: URL_ROOT + TEST_LIB + ":19",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "function() {\n" +
-                 "  return typeof n !== \"undefined\" && !n.event.triggered ? n.event.handle.apply(arguments.callee.elem, arguments) : g\n" +
-                 "}"
-      },
-      {
         type: "keydown",
         filename: TEST_URL + ":36",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler9 = function divKeyDown() {\n" +
+        handler: "function divKeyDown() {\n" +
                  "  alert(9);\n" +
                  "}"
-      },
-      {
-        type: "keydown",
-        filename: URL_ROOT + TEST_LIB + ":19",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "function() {\n" +
-                 "  return typeof n !== \"undefined\" && !n.event.triggered ? n.event.handle.apply(arguments.callee.elem, arguments) : g\n" +
-                 "}"
       }
     ]
   },
   {
     selector: "#livediv",
     expected: [
       {
         type: "dblclick",
         filename: TEST_URL + ":28",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler1 = function liveDivDblClick() {\n" +
-                 "  alert(1);\n" +
+        handler: "function() {\n" +
+                 "  return E.apply(this, arguments)\n" +
                  "}"
       },
       {
         type: "dragstart",
         filename: TEST_URL + ":29",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler2 = function liveDivDragStart() {\n" +
-                 "  alert(2);\n" +
+        handler: "function() {\n" +
+                 "  return E.apply(this, arguments)\n" +
                  "}"
       }
     ]
   },
 ];
 /*eslint-enable */
 
 add_task(function* () {
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.4.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.4.js
@@ -80,206 +80,142 @@ const TEST_DATA = [
       },
       {
         type: "load",
         filename: URL_ROOT + TEST_LIB + ":26",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "ready: function() {\n" +
+        handler: "function() {\n" +
                  "  if (!c.isReady) {\n" +
                  "    if (!s.body) return setTimeout(c.ready, 13);\n" +
                  "    c.isReady = true;\n" +
                  "    if (Q) {\n" +
                  "      for (var a, b = 0; a = Q[b++];) a.call(s, c);\n" +
                  "      Q = null\n" +
                  "    }\n" +
                  "    c.fn.triggerHandler && c(s).triggerHandler(\"ready\")\n" +
                  "  }\n" +
                  "}"
+      },
+      {
+        type: "dblclick",
+        filename: URL_ROOT + TEST_LIB + ":31",
+        attributes: [
+          "jQuery"
+        ],
+        handler: "function() {\n" +
+                 "  return a.apply(d || this, arguments)\n" +
+                 "}"
+      },
+      {
+        type: "DOMContentLoaded",
+        filename: URL_ROOT + TEST_LIB + ":32",
+        attributes: [
+          "Bubbling",
+          "DOM2"
+        ],
+        handler: "function() {\n" +
+                 "  s.removeEventListener(\"DOMContentLoaded\", M, false);\n" +
+                 "  c.ready()\n" +
+                 "}"
+      },
+      {
+        type: "dragstart",
+        filename: URL_ROOT + TEST_LIB + ":31",
+        attributes: [
+          "jQuery"
+        ],
+        handler: "function() {\n" +
+                 "  return a.apply(d || this, arguments)\n" +
+                 "}"
       }
     ]
   },
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":34",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler7 = function divClick1() {\n" +
+        handler: "function divClick1() {\n" +
                  "  alert(7);\n" +
                  "}"
       },
       {
         type: "click",
         filename: TEST_URL + ":35",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler8 = function divClick2() {\n" +
+        handler: "function divClick2() {\n" +
                  "  alert(8);\n" +
                  "}"
       },
       {
-        type: "click",
-        filename: URL_ROOT + TEST_LIB + ":48",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "j = function() {\n" +
-                 "  return typeof c !== \"undefined\" && !c.event.triggered ? c.event.handle.apply(j.elem, arguments) : w\n" +
-                 "}"
-      },
-      {
         type: "keydown",
         filename: TEST_URL + ":36",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler9 = function divKeyDown() {\n" +
+        handler: "function divKeyDown() {\n" +
                  "  alert(9);\n" +
                  "}"
-      },
-      {
-        type: "keydown",
-        filename: URL_ROOT + TEST_LIB + ":48",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "j = function() {\n" +
-                 "  return typeof c !== \"undefined\" && !c.event.triggered ? c.event.handle.apply(j.elem, arguments) : w\n" +
-                 "}"
       }
     ]
   },
   {
     selector: "#livediv",
     expected: [
       {
         type: "dblclick",
         filename: TEST_URL + ":28",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler1 = function liveDivDblClick() {\n" +
-                 "  alert(1);\n" +
+        handler: "function() {\n" +
+                 "  return a.apply(d || this, arguments)\n" +
                  "}"
       },
       {
         type: "dblclick",
         filename: URL_ROOT + TEST_LIB + ":17",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "function qa(a) {\n" +
-                 "  var b = true,\n" +
-                 "    d = [],\n" +
-                 "    f = [],\n" +
-                 "    e = arguments,\n" +
-                 "    i, j, o, p, n, t = c.extend({}, c.data(this, \"events\").live);\n" +
-                 "  for (p in t) {\n" +
-                 "    j = t[p];\n" +
-                 "    if (j.live === a.type || j.altLive && c.inArray(a.type, j.altLive) > -1) {\n" +
-                 "      i = j.data;\n" +
-                 "      i.beforeFilter && i.beforeFilter[a.type] && !i.beforeFilter[a.type](a) || f.push(j.selector)\n" +
-                 "    } else delete t[p]\n" +
-                 "  }\n" +
-                 "  i = c(a.target).closest(f, a.currentTarget);\n" +
-                 "  n = 0;\n" +
-                 "  for (l = i.length; n < l; n++)\n" +
-                 "    for (p in t) {\n" +
-                 "      j = t[p];\n" +
-                 "      o = i[n].elem;\n" +
-                 "      f = null;\n" +
-                 "      if (i[n].selector === j.selector) {\n" +
-                 "        if (j.live === \"mouseenter\" || j.live === \"mouseleave\") f = c(a.relatedTarget).closest(j.selector)[0];\n" +
-                 "        if (!f || f !== o) d.push({\n" +
-                 "          elem: o,\n" +
-                 "          fn: j\n" +
-                 "        })\n" +
-                 "      }\n" +
-                 "    }\n" +
-                 "  n = 0;\n" +
-                 "  for (l = d.length; n < l; n++) {\n" +
-                 "    i = d[n];\n" +
-                 "    a.currentTarget = i.elem;\n" +
-                 "    a.data = i.fn.data;\n" +
-                 "    if (i.fn.apply(i.elem, e) === false) {\n" +
-                 "      b = false;\n" +
-                 "      break\n" +
-                 "    }\n" +
-                 "  }\n" +
-                 "  return b\n" +
+        handler: "function() {\n" +
+                 "  return a.apply(d || this, arguments)\n" +
                  "}"
       },
       {
         type: "dragstart",
         filename: TEST_URL + ":29",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler2 = function liveDivDragStart() {\n" +
-                 "  alert(2);\n" +
+        handler: "function() {\n" +
+                 "  return a.apply(d || this, arguments)\n" +
                  "}"
       },
       {
         type: "dragstart",
         filename: URL_ROOT + TEST_LIB + ":17",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "function qa(a) {\n" +
-                 "  var b = true,\n" +
-                 "    d = [],\n" +
-                 "    f = [],\n" +
-                 "    e = arguments,\n" +
-                 "    i, j, o, p, n, t = c.extend({}, c.data(this, \"events\").live);\n" +
-                 "  for (p in t) {\n" +
-                 "    j = t[p];\n" +
-                 "    if (j.live === a.type || j.altLive && c.inArray(a.type, j.altLive) > -1) {\n" +
-                 "      i = j.data;\n" +
-                 "      i.beforeFilter && i.beforeFilter[a.type] && !i.beforeFilter[a.type](a) || f.push(j.selector)\n" +
-                 "    } else delete t[p]\n" +
-                 "  }\n" +
-                 "  i = c(a.target).closest(f, a.currentTarget);\n" +
-                 "  n = 0;\n" +
-                 "  for (l = i.length; n < l; n++)\n" +
-                 "    for (p in t) {\n" +
-                 "      j = t[p];\n" +
-                 "      o = i[n].elem;\n" +
-                 "      f = null;\n" +
-                 "      if (i[n].selector === j.selector) {\n" +
-                 "        if (j.live === \"mouseenter\" || j.live === \"mouseleave\") f = c(a.relatedTarget).closest(j.selector)[0];\n" +
-                 "        if (!f || f !== o) d.push({\n" +
-                 "          elem: o,\n" +
-                 "          fn: j\n" +
-                 "        })\n" +
-                 "      }\n" +
-                 "    }\n" +
-                 "  n = 0;\n" +
-                 "  for (l = d.length; n < l; n++) {\n" +
-                 "    i = d[n];\n" +
-                 "    a.currentTarget = i.elem;\n" +
-                 "    a.data = i.fn.data;\n" +
-                 "    if (i.fn.apply(i.elem, e) === false) {\n" +
-                 "      b = false;\n" +
-                 "      break\n" +
-                 "    }\n" +
-                 "  }\n" +
-                 "  return b\n" +
+        handler: "function() {\n" +
+                 "  return a.apply(d || this, arguments)\n" +
                  "}"
       }
     ]
   },
 ];
 /*eslint-enable */
 
 add_task(function* () {
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.6.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.6.js
@@ -82,95 +82,84 @@ const TEST_DATA = [
       },
       {
         type: "load",
         filename: URL_ROOT + TEST_LIB + ":16",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "ready: function(a) {\n" +
+        handler: "function(a) {\n" +
                  "  if (a === !0 && !--e.readyWait || a !== !0 && !e.isReady) {\n" +
                  "    if (!c.body) return setTimeout(e.ready, 1);\n" +
                  "    e.isReady = !0;\n" +
                  "    if (a !== !0 && --e.readyWait > 0) return;\n" +
                  "    y.resolveWith(c, [e]), e.fn.trigger && e(c).trigger(\"ready\").unbind(\"ready\")\n" +
                  "  }\n" +
                  "}"
+      },
+      {
+        type: "DOMContentLoaded",
+        filename: URL_ROOT + TEST_LIB + ":16",
+        attributes: [
+          "Bubbling",
+          "DOM2"
+        ],
+        handler: "function() {\n" +
+                 "  c.removeEventListener(\"DOMContentLoaded\", z, !1), e.ready()\n" +
+                 "}"
       }
     ]
   },
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":34",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler7 = function divClick1() {\n" +
+        handler: "function divClick1() {\n" +
                  "  alert(7);\n" +
                  "}"
       },
       {
         type: "click",
         filename: TEST_URL + ":35",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler8 = function divClick2() {\n" +
+        handler: "function divClick2() {\n" +
                  "  alert(8);\n" +
                  "}"
       },
       {
-        type: "click",
-        filename: URL_ROOT + TEST_LIB + ":16",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "i.handle = k = function(a) {\n" +
-                 "  return typeof f != \"undefined\" && (!a || f.event.triggered !== a.type) ? f.event.handle.apply(k.elem, arguments) : b\n" +
-                 "}"
-      },
-      {
         type: "keydown",
         filename: TEST_URL + ":36",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler9 = function divKeyDown() {\n" +
+        handler: "function divKeyDown() {\n" +
                  "  alert(9);\n" +
                  "}"
-      },
-      {
-        type: "keydown",
-        filename: URL_ROOT + TEST_LIB + ":16",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "i.handle = k = function(a) {\n" +
-                 "  return typeof f != \"undefined\" && (!a || f.event.triggered !== a.type) ? f.event.handle.apply(k.elem, arguments) : b\n" +
-                 "}"
       }
     ]
   },
   {
     selector: "#livediv",
     expected: [
       {
         type: "dblclick",
         filename: TEST_URL + ":28",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler1 = function liveDivDblClick() {\n" +
+        handler: "function liveDivDblClick() {\n" +
                  "  alert(1);\n" +
                  "}"
       },
       {
         type: "dblclick",
         filename: URL_ROOT + TEST_LIB + ":16",
         attributes: [
           "jQuery",
@@ -215,17 +204,17 @@ const TEST_DATA = [
       },
       {
         type: "dragend",
         filename: TEST_URL + ":31",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler4 = function liveDivDragEnd() {\n" +
+        handler: "function liveDivDragEnd() {\n" +
                  "  alert(4);\n" +
                  "}"
       },
       {
         type: "dragend",
         filename: URL_ROOT + TEST_LIB + ":16",
         attributes: [
           "jQuery",
@@ -270,17 +259,17 @@ const TEST_DATA = [
       },
       {
         type: "dragleave",
         filename: TEST_URL + ":30",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler3 = function liveDivDragLeave() {\n" +
+        handler: "function liveDivDragLeave() {\n" +
                  "  alert(3);\n" +
                  "}"
       },
       {
         type: "dragleave",
         filename: URL_ROOT + TEST_LIB + ":16",
         attributes: [
           "jQuery",
@@ -325,17 +314,17 @@ const TEST_DATA = [
       },
       {
         type: "dragstart",
         filename: TEST_URL + ":29",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler2 = function liveDivDragStart() {\n" +
+        handler: "function liveDivDragStart() {\n" +
                  "  alert(2);\n" +
                  "}"
       },
       {
         type: "dragstart",
         filename: URL_ROOT + TEST_LIB + ":16",
         attributes: [
           "jQuery",
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.7.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_1.7.js
@@ -82,150 +82,139 @@ const TEST_DATA = [
       },
       {
         type: "load",
         filename: URL_ROOT + TEST_LIB + ":2",
         attributes: [
           "Bubbling",
           "DOM2"
         ],
-        handler: "ready: function(a) {\n" +
+        handler: "function(a) {\n" +
                  "  if (a === !0 && !--e.readyWait || a !== !0 && !e.isReady) {\n" +
                  "    if (!c.body) return setTimeout(e.ready, 1);\n" +
                  "    e.isReady = !0;\n" +
                  "    if (a !== !0 && --e.readyWait > 0) return;\n" +
                  "    B.fireWith(c, [e]), e.fn.trigger && e(c).trigger(\"ready\").unbind(\"ready\")\n" +
                  "  }\n" +
                  "}"
+      },
+      {
+        type: "DOMContentLoaded",
+        filename: URL_ROOT + TEST_LIB + ":2",
+        attributes: [
+          "Bubbling",
+          "DOM2"
+        ],
+        handler: "function() {\n" +
+                 "  c.removeEventListener(\"DOMContentLoaded\", C, !1), e.ready()\n" +
+                 "}"
       }
     ]
   },
   {
     selector: "#testdiv",
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":34",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler7 = function divClick1() {\n" +
+        handler: "function divClick1() {\n" +
                  "  alert(7);\n" +
                  "}"
       },
       {
         type: "click",
         filename: TEST_URL + ":35",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler8 = function divClick2() {\n" +
+        handler: "function divClick2() {\n" +
                  "  alert(8);\n" +
                  "}"
       },
       {
-        type: "click",
-        filename: URL_ROOT + TEST_LIB + ":3",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "h.handle = i = function(a) {\n" +
-                 "  return typeof f != \"undefined\" && (!a || f.event.triggered !== a.type) ? f.event.dispatch.apply(i.elem, arguments) : b\n" +
-                 "}"
-      },
-      {
         type: "keydown",
         filename: TEST_URL + ":36",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler9 = function divKeyDown() {\n" +
+        handler: "function divKeyDown() {\n" +
                  "  alert(9);\n" +
                  "}"
-      },
-      {
-        type: "keydown",
-        filename: URL_ROOT + TEST_LIB + ":3",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "h.handle = i = function(a) {\n" +
-                 "  return typeof f != \"undefined\" && (!a || f.event.triggered !== a.type) ? f.event.dispatch.apply(i.elem, arguments) : b\n" +
-                 "}"
       }
     ]
   },
   {
     selector: "#livediv",
     expected: [
       {
         type: "dblclick",
         filename: TEST_URL + ":28",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler1 = function liveDivDblClick() {\n" +
+        handler: "function liveDivDblClick() {\n" +
                  "  alert(1);\n" +
                  "}"
       },
       {
         type: "dragend",
         filename: TEST_URL + ":31",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler4 = function liveDivDragEnd() {\n" +
+        handler: "function liveDivDragEnd() {\n" +
                  "  alert(4);\n" +
                  "}"
       },
       {
         type: "dragleave",
         filename: TEST_URL + ":30",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler3 = function liveDivDragLeave() {\n" +
+        handler: "function liveDivDragLeave() {\n" +
                  "  alert(3);\n" +
                  "}"
       },
       {
         type: "dragover",
         filename: TEST_URL + ":33",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler6 = function liveDivDragOver() {\n" +
+        handler: "function liveDivDragOver() {\n" +
                  "  alert(6);\n" +
                  "}"
       },
       {
         type: "dragstart",
         filename: TEST_URL + ":29",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler2 = function liveDivDragStart() {\n" +
+        handler: "function liveDivDragStart() {\n" +
                  "  alert(2);\n" +
                  "}"
       },
       {
         type: "drop",
         filename: TEST_URL + ":32",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler5 = function liveDivDrop() {\n" +
+        handler: "function liveDivDrop() {\n" +
                  "  alert(5);\n" +
                  "}"
       }
     ]
   },
 ];
 /*eslint-enable */
 
--- a/devtools/client/inspector/markup/test/browser_markup_events_jquery_2.1.1.js
+++ b/devtools/client/inspector/markup/test/browser_markup_events_jquery_2.1.1.js
@@ -86,108 +86,86 @@ const TEST_DATA = [
     selector: "#testdiv",
     expected: [
       {
         type: "click",
         filename: TEST_URL + ":34",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler7 = function divClick1() {\n" +
+        handler: "function divClick1() {\n" +
                  "  alert(7);\n" +
                  "}"
       },
       {
         type: "click",
         filename: TEST_URL + ":35",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler8 = function divClick2() {\n" +
+        handler: "function divClick2() {\n" +
                  "  alert(8);\n" +
                  "}"
       },
       {
-        type: "click",
-        filename: URL_ROOT + TEST_LIB + ":3",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "g = r.handle = function(b) {\n" +
-                 "  return typeof n !== U && n.event.triggered !== b.type ? n.event.dispatch.apply(a, arguments) : void 0\n" +
-                 "}"
-      },
-      {
         type: "keydown",
         filename: TEST_URL + ":36",
         attributes: [
           "jQuery"
         ],
-        handler: "var handler9 = function divKeyDown() {\n" +
+        handler: "function divKeyDown() {\n" +
                  "  alert(9);\n" +
                  "}"
-      },
-      {
-        type: "keydown",
-        filename: URL_ROOT + TEST_LIB + ":3",
-        attributes: [
-          "Bubbling",
-          "DOM2"
-        ],
-        handler: "g = r.handle = function(b) {\n" +
-                 "  return typeof n !== U && n.event.triggered !== b.type ? n.event.dispatch.apply(a, arguments) : void 0\n" +
-                 "}"
       }
     ]
   },
   {
     selector: "#livediv",
     expected: [
       {
         type: "dragend",
         filename: TEST_URL + ":31",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler4 = function liveDivDragEnd() {\n" +
+        handler: "function liveDivDragEnd() {\n" +
                  "  alert(4);\n" +
                  "}"
       },
       {
         type: "dragleave",
         filename: TEST_URL + ":30",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler3 = function liveDivDragLeave() {\n" +
+        handler: "function liveDivDragLeave() {\n" +
                  "  alert(3);\n" +
                  "}"
       },
       {
         type: "dragover",
         filename: TEST_URL + ":33",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler6 = function liveDivDragOver() {\n" +
+        handler: "function liveDivDragOver() {\n" +
                  "  alert(6);\n" +
                  "}"
       },
       {
         type: "drop",
         filename: TEST_URL + ":32",
         attributes: [
           "jQuery",
           "Live"
         ],
-        handler: "var handler5 = function liveDivDrop() {\n" +
+        handler: "function liveDivDrop() {\n" +
                  "  alert(5);\n" +
                  "}"
       }
     ]
   },
 ];
 /*eslint-enable */
 
rename from devtools/client/inspector/markup/test/doc_markup_events1.html
rename to devtools/client/inspector/markup/test/doc_markup_events_01.html
rename from devtools/client/inspector/markup/test/doc_markup_events2.html
rename to devtools/client/inspector/markup/test/doc_markup_events_02.html
rename from devtools/client/inspector/markup/test/doc_markup_events3.html
rename to devtools/client/inspector/markup/test/doc_markup_events_03.html
--- a/devtools/client/inspector/markup/test/doc_markup_events3.html
+++ b/devtools/client/inspector/markup/test/doc_markup_events_03.html
@@ -3,20 +3,17 @@
   <head>
     <meta charset="utf-8">
     <style>
     #es6-method,
     #generator,
     #anon-generator,
     #named-function-expression,
     #anon-function-expression,
-    #returned-function,
-    #constructed-function,
-    #constructed-function-with-body-string,
-    #multiple-assignment {
+    #returned-function {
       border: 1px solid #000;
       width: 200px;
       min-height: 1em;
       cursor: pointer;
     }
     </style>
     <script type="application/javascript;version=1.8">
       let namedFunctionExpression =
@@ -29,29 +26,20 @@
       };
 
       let returnedFunction = (function() {
         return function bar() {
           alert("returnedFunction");
         }
       })();
 
-      let constructedFunc = new Function();
-
-      let constructedFuncWithBodyString =
-        new Function('a', 'b', 'c', 'alert("constructedFuncWithBodyString");');
-
-      let multipleAssignment = foo = bar = function multi() {
-        alert("multipleAssignment");
-      }
-
       function init() {
-        let he = new handleEventClick();
+        let em = new Es6Method();
         let es6Method = document.getElementById("es6-method");
-        es6Method.addEventListener("click", he.es6Method);
+        es6Method.addEventListener("click", em.es6Method);
 
         let generatorNode = document.getElementById("generator");
         generatorNode.addEventListener("click", generator);
 
         let anonGenerator = document.getElementById("anon-generator");
         anonGenerator.addEventListener("click", function* () {
           alert("anonGenerator");
         });
@@ -63,53 +51,49 @@
 
         let anonFunctionExpressionNode =
           document.getElementById("anon-function-expression");
         anonFunctionExpressionNode.addEventListener("click",
                                                      anonFunctionExpression);
 
         let returnedFunctionNode = document.getElementById("returned-function");
         returnedFunctionNode.addEventListener("click", returnedFunction);
-
-        let constructedFunctionNode =
-          document.getElementById("constructed-function");
-        constructedFunctionNode.addEventListener("click", constructedFunc);
-
-        let constructedFunctionWithBodyStringNode =
-          document.getElementById("constructed-function-with-body-string");
-        constructedFunctionWithBodyStringNode
-          .addEventListener("click", constructedFuncWithBodyString);
-
-        let multipleAssignmentNode =
-          document.getElementById("multiple-assignment");
-        multipleAssignmentNode.addEventListener("click", multipleAssignment);
       }
 
-      function handleEventClick(hehe) {
+      function Es6Method(hehe) {
 
       }
 
-      handleEventClick.prototype = {
-        es6Method() {
+      Es6Method.prototype = {
+        es6Method(foo, bar) {
           alert("obj.es6Method");
         }
       };
 
+      function HandleEvent() {
+        let handleEventNode = document.getElementById("handleEvent");
+        handleEventNode.addEventListener("click", this);
+      }
+
+      HandleEvent.prototype = {
+        handleEvent: function(event) {
+          switch (event.type) {
+            case "click":
+              alert("handleEvent click");
+          }
+        }
+      };
+
       function* generator() {
         alert("generator");
       }
     </script>
   </head>
   <body onload="init();">
     <h1>Events test 3</h1>
     <div id="es6-method">ES6 method</div>
     <div id="generator">Generator</div>
     <div id="anon-generator">Anonymous Generator</div>
     <div id="named-function-expression">Named Function Expression</div>
     <div id="anon-function-expression">Anonymous Function Expression</div>
     <div id="returned-function">Returned Function</div>
-    <div id="constructed-function">Constructed Function</div>
-    <div id="constructed-function-with-body-string">
-      Constructed Function with body string
-    </div>
-    <div id="multiple-assignment">Multiple Assignment</div>
   </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/doc_markup_events_04.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <style>
+    #constructed-function,
+    #constructed-function-with-body-string,
+    #multiple-assignment,
+    #promise,
+    #arraysort,
+    #handleEvent {
+      border: 1px solid #000;
+      width: 200px;
+      min-height: 1em;
+      cursor: pointer;
+    }
+    </style>
+    <script type="application/javascript;version=1.8">
+      let constructedFunc = new Function();
+
+      let constructedFuncWithBodyString =
+        new Function('a', 'b', 'c', 'alert("constructedFuncWithBodyString");');
+
+      let multipleAssignment = foo = bar = function multi() {
+        alert("multipleAssignment");
+      }
+
+      function init() {
+        let constructedFunctionNode =
+          document.getElementById("constructed-function");
+        constructedFunctionNode.addEventListener("click", constructedFunc);
+
+        let constructedFunctionWithBodyStringNode =
+          document.getElementById("constructed-function-with-body-string");
+        constructedFunctionWithBodyStringNode
+          .addEventListener("click", constructedFuncWithBodyString);
+
+        let multipleAssignmentNode =
+          document.getElementById("multiple-assignment");
+        multipleAssignmentNode.addEventListener("click", multipleAssignment);
+
+        let promiseNode = document.getElementById("promise");
+        new Promise((resolve, reject) => {
+          promiseNode.addEventListener("click", resolve);
+        });
+
+        let arraySortNode = document.getElementById("arraysort");
+        arraySortNode.addEventListener("click", Array.sort);
+
+        new HandleEvent();
+
+        document.addEventListener("click", function(foo, bar) {
+          alert("document event listener clicked");
+        });
+
+        document.documentElement.addEventListener("click", function(foo2, bar2) {
+          alert("documentElement event listener clicked");
+        });
+      }
+
+      function Es6Method(hehe) {
+
+      }
+
+      Es6Method.prototype = {
+        es6Method(foo, bar) {
+          alert("obj.es6Method");
+        }
+      };
+
+      function HandleEvent() {
+        let handleEventNode = document.getElementById("handleEvent");
+        handleEventNode.addEventListener("click", this);
+      }
+
+      HandleEvent.prototype = {
+        handleEvent: function(event) {
+          switch (event.type) {
+            case "click":
+              alert("handleEvent click");
+          }
+        }
+      };
+
+      function* generator() {
+        alert("generator");
+      }
+    </script>
+  </head>
+  <body onload="init();">
+    <h1>Events test 4</h1>
+    <div id="constructed-function">Constructed Function</div>
+    <div id="constructed-function-with-body-string">
+      Constructed Function with body string
+    </div>
+    <div id="multiple-assignment">Multiple Assignment</div>
+    <div id="promise">Promise</div>
+    <div id="arraysort">Array.sort</div>
+    <div id="handleEvent">HandleEvent</div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/markup/test/helper_diff.js
@@ -0,0 +1,288 @@
+/**
+ * This diff utility is taken from:
+ * https://github.com/Slava/diff.js
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014 Slava
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+/* eslint-disable */
+
+/**
+ * USAGE:
+ *   diff(text1, text2);
+ */
+
+/**
+ * Longest Common Subsequence
+ *
+ * @param A - sequence of atoms - Array
+ * @param B - sequence of atoms - Array
+ * @param equals - optional comparator of atoms - returns true or false,
+ *                 if not specified, triple equals operator is used
+ * @returns Array - sequence of atoms, one of LCSs, edit script from A to B
+ */
+var LCS = function (A, B, /* optional */ equals) {
+  // We just compare atoms with default equals operator by default
+  if (equals === undefined)
+    equals = function (a, b) { return a === b; };
+
+  // NOTE: all intervals from now on are both sides inclusive
+  // Get the points in Edit Graph, one of the LCS paths goes through.
+  // The points are located on the same diagonal and represent the middle
+  // snake ([D/2] out of D+1) in the optimal edit path in edit graph.
+  // @param startA, endA - substring of A we are working on
+  // @param startB, endB - substring of B we are working on
+  // @returns Array - [
+  //                   [x, y], - beginning of the middle snake
+  //                   [u, v], - end of the middle snake
+  //                    D,     - optimal edit distance
+  //                    LCS ]  - length of LCS
+  var findMidSnake = function (startA, endA, startB, endB) {
+    var N = endA - startA + 1;
+    var M = endB - startB + 1;
+    var Max = N + M;
+    var Delta = N - M;
+    var halfMaxCeil = (Max + 1) / 2 | 0;
+
+    var foundOverlap = false;
+    var overlap = null;
+
+    // Maps -Max .. 0 .. +Max, diagonal index to endpoints for furthest reaching
+    // D-path on current iteration.
+    var V = {};
+    // Same but for reversed paths.
+    var U = {};
+
+    // Special case for the base case, D = 0, k = 0, x = y = 0
+    V[1] = 0;
+    // Special case for the base case reversed, D = 0, k = 0, x = N, y = M
+    U[Delta - 1] = N;
+
+    // Iterate over each possible length of edit script
+    for (var D = 0; D <= halfMaxCeil; D++) {
+      // Iterate over each diagonal
+      for (var k = -D; k <= D && !overlap; k += 2) {
+        // Positions in sequences A and B of furthest going D-path on diagonal k.
+        var x, y;
+
+        // Choose from each diagonal we extend
+        if (k === -D || (k !== D && V[k - 1] < V[k + 1]))
+          // Extending path one point down, that's why x doesn't change, y
+          // increases implicitly
+          x = V[k + 1];
+        else
+          // Extending path one point to the right, x increases
+          x = V[k - 1] + 1;
+
+        // We can calculate the y out of x and diagonal index.
+        y = x - k;
+
+        if (isNaN(y) || x > N || y > M)
+          continue;
+
+        var xx = x;
+        // Try to extend the D-path with diagonal paths. Possible only if atoms
+        // A_x match B_y
+        while (x < N && y < M // if there are atoms to compare
+               && equals(A[startA + x], B[startB + y])) {
+          x++; y++;
+        }
+
+        // We can safely update diagonal k, since on every iteration we consider
+        // only even or only odd diagonals and the result of one depends only on
+        // diagonals of different iteration.
+        V[k] = x;
+
+        // Check feasibility, Delta is checked for being odd.
+        if ((Delta & 1) === 1 && inRange(k, Delta - (D - 1), Delta + (D - 1)))
+          // Forward D-path can overlap with reversed D-1-path
+          if (V[k] >= U[k])
+            // Found an overlap, the middle snake, convert X-components to dots
+            overlap = [xx, x].map(toPoint, k); // XXX ES5
+      }
+
+      if (overlap)
+        var SES = D * 2 - 1;
+
+      // Iterate over each diagonal for reversed case
+      for (var k = -D; k <= D && !overlap; k += 2) {
+        // The real diagonal we are looking for is k + Delta
+        var K = k + Delta;
+        var x, y;
+        if (k === D || (k !== -D && U[K - 1] < U[K + 1]))
+          x = U[K - 1];
+        else
+          x = U[K + 1] - 1;
+
+        y = x - K;
+        if (isNaN(y) || x < 0 || y < 0)
+          continue;
+        var xx = x;
+        while (x > 0 && y > 0 && equals(A[startA + x - 1], B[startB + y - 1])) {
+          x--; y--;
+        }
+        U[K] = x;
+
+        if (Delta % 2 === 0 && inRange(K, -D, D))
+          if (U[K] <= V[K])
+            overlap = [x, xx].map(toPoint, K); // XXX ES5
+      }
+
+      if (overlap) {
+        SES = SES || D * 2;
+        // Remember we had offset of each sequence?
+        for (var i = 0; i < 2; i++) for (var j = 0; j < 2; j++)
+          overlap[i][j] += [startA, startB][j] - i;
+        return overlap.concat([ SES, (Max - SES) / 2 ]);
+      }
+    }
+  };
+
+  var lcsAtoms = [];
+  var lcs = function (startA, endA, startB, endB) {
+    var N = endA - startA + 1;
+    var M = endB - startB + 1;
+
+    if (N > 0 && M > 0) {
+      var middleSnake = findMidSnake(startA, endA, startB, endB);
+      // A[x;u] == B[y,v] and is part of LCS
+      var x = middleSnake[0][0], y = middleSnake[0][1];
+      var u = middleSnake[1][0], v = middleSnake[1][1];
+      var D = middleSnake[2];
+
+      if (D > 1) {
+        lcs(startA, x - 1, startB, y - 1);
+        if (x <= u) {
+          [].push.apply(lcsAtoms, A.slice(x, u + 1));
+        }
+        lcs(u + 1, endA, v + 1, endB);
+      } else if (M > N)
+        [].push.apply(lcsAtoms, A.slice(startA, endA + 1));
+      else
+        [].push.apply(lcsAtoms, B.slice(startB, endB + 1));
+    }
+  };
+
+  lcs(0, A.length - 1, 0, B.length - 1);
+  return lcsAtoms;
+};
+
+// Helpers
+var inRange = function (x, l, r) {
+  return (l <= x && x <= r) || (r <= x && x <= l);
+};
+
+// Takes X-component as argument, diagonal as context,
+// returns array-pair of form x, y
+var toPoint = function (x) {
+  return [x, x - this];  // XXX context is not the best way to pass diagonal
+};
+
+// Wrappers
+LCS.StringLCS = function (A, B) {
+  return LCS(A.split(''), B.split('')).join('');
+};
+
+/**
+ * Diff sequence
+ *
+ * @param A - sequence of atoms - Array
+ * @param B - sequence of atoms - Array
+ * @param equals - optional comparator of atoms - returns true or false,
+ *                 if not specified, triple equals operator is used
+ * @returns Array - sequence of objects in a form of:
+ *   - operation: one of "none", "add", "delete"
+ *   - atom: the atom found in either A or B
+ * Applying operations from diff sequence you should be able to transform A to B
+ */
+function diff(A, B, equals) {
+  // We just compare atoms with default equals operator by default
+  if (equals === undefined)
+    equals = function (a, b) { return a === b; };
+
+  var diff = [];
+  var i = 0, j = 0;
+  var N = A.length, M = B.length, K = 0;
+
+  while (i < N && j < M && equals(A[i], B[j]))
+    i++, j++;
+
+  while (i < N && j < M && equals(A[N-1], B[M-1]))
+    N--, M--, K++;
+
+  [].push.apply(diff, A.slice(0, i).map(function (atom) {
+    return { operation: "none", atom: atom }; }));
+
+  var lcs = LCS(A.slice(i, N), B.slice(j, M), equals);
+
+  for (var k = 0; k < lcs.length; k++) {
+    var atom = lcs[k];
+    var ni = customIndexOf.call(A, atom, i, equals);
+    var nj = customIndexOf.call(B, atom, j, equals);
+
+    // XXX ES5 map
+    // Delete unmatched atoms from A
+    [].push.apply(diff, A.slice(i, ni).map(function (atom) {
+      return { operation: "delete", atom: atom };
+    }));
+
+    // Add unmatched atoms from B
+    [].push.apply(diff, B.slice(j, nj).map(function (atom) {
+      return { operation: "add", atom: atom };
+    }));
+
+    // Add the atom found in both sequences
+    diff.push({ operation: "none", atom: atom });
+
+    i = ni + 1;
+    j = nj + 1;
+  }
+
+  // Don't forget about the rest
+
+  [].push.apply(diff, A.slice(i, N).map(function (atom) {
+    return { operation: "delete", atom: atom };
+  }));
+
+  [].push.apply(diff, B.slice(j, M).map(function (atom) {
+    return { operation: "add", atom: atom };
+  }));
+
+  [].push.apply(diff, A.slice(N, N + K).map(function (atom) {
+    return { operation: "none", atom: atom }; }));
+
+  return diff;
+};
+
+// Accepts custom comparator
+var customIndexOf = function(item, start, equals){
+  var arr = this;
+  for (var i = start; i < arr.length; i++)
+    if (equals(item, arr[i]))
+      return i;
+  return -1;
+};
+
+function textDiff(text1, text2) {
+  return diff(text1.split("\n"), text2.split("\n"));
+}
--- a/devtools/client/inspector/markup/test/helper_events_test_runner.js
+++ b/devtools/client/inspector/markup/test/helper_events_test_runner.js
@@ -1,15 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 /* eslint no-unused-vars: [2, {"vars": "local"}] */
 /* import-globals-from head.js */
+/* import-globals-from helper_diff.js */
 "use strict";
 
+loadHelperScript("helper_diff.js");
+
 /**
  * Generator function that runs checkEventsForNode() for each object in the
  * TEST_DATA array.
  */
 function* runEventPopupTests(url, tests) {
   let {inspector, testActor} = yield openInspectorForURL(url);
 
   yield inspector.markup.expandAll();
@@ -78,16 +81,18 @@ function* checkEventsForNode(test, inspe
     info("Processing header[" + i + "] for " + cssSelector);
 
     let header = headers[i];
     let type = header.querySelector(".event-tooltip-event-type");
     let filename = header.querySelector(".event-tooltip-filename");
     let attributes = header.querySelectorAll(".event-tooltip-attributes");
     let contentBox = header.nextElementSibling;
 
+    info("Looking for " + type.textContent);
+
     is(type.textContent, expected[i].type,
        "type matches for " + cssSelector);
     is(filename.textContent, expected[i].filename,
        "filename matches for " + cssSelector);
 
     is(attributes.length, expected[i].attributes.length,
        "we have the correct number of attributes");
 
@@ -98,14 +103,52 @@ function* checkEventsForNode(test, inspe
 
     // Make sure the header is not hidden by scrollbars before clicking.
     header.scrollIntoView();
 
     EventUtils.synthesizeMouseAtCenter(header, {}, type.ownerGlobal);
     yield tooltip.once("event-tooltip-ready");
 
     let editor = tooltip.eventTooltip._eventEditors.get(contentBox).editor;
-    is(editor.getText(), expected[i].handler,
-       "handler matches for " + cssSelector);
+    testDiff(editor.getText(), expected[i].handler,
+       "handler matches for " + cssSelector, ok);
   }
 
   tooltip.hide();
 }
+
+/**
+ * Create diff of two strings.
+ *
+ * @param  {String} text1
+ *         String to compare with text2.
+ * @param  {String} text2 [description]
+ *         String to compare with text1.
+ * @param  {String} msg
+ *         Message to display on failure. A diff will be displayed after this
+ *         message.
+ */
+function testDiff(text1, text2, msg) {
+  let out = "";
+
+  if (text1 === text2) {
+    ok(true, msg);
+    return;
+  }
+
+  let result = textDiff(text1, text2);
+
+  for (let {atom, operation} of result) {
+    switch (operation) {
+      case "add":
+        out += "+ " + atom + "\n";
+        break;
+      case "delete":
+        out += "- " + atom + "\n";
+        break;
+      case "none":
+        out += "  " + atom + "\n";
+        break;
+    }
+  }
+
+  ok(false, msg + "\nDIFF:\n==========\n" + out + "==========\n");
+}
--- a/devtools/client/inspector/markup/views/markup-container.js
+++ b/devtools/client/inspector/markup/views/markup-container.js
@@ -668,16 +668,22 @@ MarkupContainer.prototype = {
     // Elements with tabindex of -1 are not focusable.
     let focusable = this.editor.elt.querySelector("[tabindex='0']");
     if (focusable) {
       focusable.focus();
     }
   },
 
   _onToggle: function (event) {
+    // Prevent the html tree from expanding when an event bubble is clicked.
+    if (event.target.dataset.event) {
+      event.stopPropagation();
+      return;
+    }
+
     this.markup.navigate(this);
     if (this.hasChildren) {
       this.markup.setNodeExpanded(this.node, !this.expanded, event.altKey);
     }
     event.stopPropagation();
   },
 
   /**
--- a/devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
+++ b/devtools/client/shared/widgets/tooltip/EventTooltipHelper.js
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/inspector.properties");
 
+const viewSource = require("devtools/client/shared/view-source");
 const Editor = require("devtools/client/sourceeditor/editor");
 const beautify = require("devtools/shared/jsbeautify/beautify");
 
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const CONTAINER_WIDTH = 500;
 
 /**
  * Set the content of a provided HTMLTooltip instance to display a list of event
@@ -74,16 +75,20 @@ EventTooltip.prototype = {
       if (!listener.hide.debugger) {
         let debuggerIcon = doc.createElementNS(XHTML_NS, "img");
         debuggerIcon.className = "event-tooltip-debugger-icon";
         debuggerIcon.setAttribute("src",
           "chrome://devtools/skin/images/tool-debugger.svg");
         let openInDebugger = L10N.getStr("eventsTooltip.openInDebugger");
         debuggerIcon.setAttribute("title", openInDebugger);
         header.appendChild(debuggerIcon);
+      } else {
+        let debuggerDiv = doc.createElementNS(XHTML_NS, "div");
+        debuggerDiv.className = "event-tooltip-debugger-spacer";
+        header.appendChild(debuggerDiv);
       }
 
       if (!listener.hide.type) {
         let eventTypeLabel = doc.createElementNS(XHTML_NS, "span");
         eventTypeLabel.className = "event-tooltip-event-type";
         eventTypeLabel.textContent = listener.type;
         eventTypeLabel.setAttribute("title", listener.type);
         header.appendChild(eventTypeLabel);
@@ -140,20 +145,20 @@ EventTooltip.prototype = {
       }
 
       // Content
       let content = doc.createElementNS(XHTML_NS, "div");
       let editor = new Editor(config);
       this._eventEditors.set(content, {
         editor: editor,
         handler: listener.handler,
-        searchString: listener.searchString,
         uri: listener.origin,
         dom0: listener.DOM0,
-        appended: false
+        native: listener.native,
+        appended: false,
       });
 
       content.className = "event-tooltip-content-box";
       this.container.appendChild(content);
 
       this._addContentListeners(header);
     }
 
@@ -217,71 +222,35 @@ EventTooltip.prototype = {
       });
     }
   },
 
   _debugClicked: function (event) {
     let header = event.currentTarget;
     let content = header.nextElementSibling;
 
-    let {uri, searchString, dom0} = this._eventEditors.get(content);
+    let {uri} = this._eventEditors.get(content);
 
     if (uri && uri !== "?") {
       // Save a copy of toolbox as it will be set to null when we hide the tooltip.
       let toolbox = this._toolbox;
 
       this._tooltip.hide();
 
       uri = uri.replace(/"/g, "");
 
-      let showSource = ({ DebuggerView }) => {
-        let matches = uri.match(/(.*):(\d+$)/);
-        let line = 1;
-
-        if (matches) {
-          uri = matches[1];
-          line = matches[2];
-        }
-
-        let item = DebuggerView.Sources.getItemForAttachment(a => a.source.url === uri);
-        if (item) {
-          let actor = item.attachment.source.actor;
-          DebuggerView.setEditorLocation(
-            actor, line, {noDebug: true}
-          ).then(() => {
-            if (dom0) {
-              let text = DebuggerView.editor.getText();
-              let index = text.indexOf(searchString);
-              let lastIndex = text.lastIndexOf(searchString);
+      let matches = uri.match(/(.*):(\d+$)/);
+      let line = 1;
 
-              // To avoid confusion we only search for DOM0 event handlers when
-              // there is only one possible match in the file.
-              if (index !== -1 && index === lastIndex) {
-                text = text.substr(0, index);
-                let newlineMatches = text.match(/\n/g);
+      if (matches) {
+        uri = matches[1];
+        line = matches[2];
+      }
 
-                if (newlineMatches) {
-                  DebuggerView.editor.setCursor({
-                    line: newlineMatches.length
-                  });
-                }
-              }
-            }
-          });
-        }
-      };
-
-      let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger");
-      toolbox.selectTool("jsdebugger").then(({ panelWin: dbg }) => {
-        if (debuggerAlreadyOpen) {
-          showSource(dbg);
-        } else {
-          dbg.once(dbg.EVENTS.SOURCES_ADDED, () => showSource(dbg));
-        }
-      });
+      viewSource.viewSourceInDebugger(toolbox, uri, line);
     }
   },
 
   destroy: function () {
     if (this._tooltip) {
       this._tooltip.off("hidden", this.destroy);
 
       let boxes = this.container.querySelectorAll(".event-tooltip-content-box");
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -287,17 +287,18 @@
   flex-shrink: 1;
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
   /* Force ellipsis to be displayed on the left */
   direction: rtl;
 }
 
-.event-tooltip-debugger-icon {
+.event-tooltip-debugger-icon,
+.event-tooltip-debugger-spacer {
   width: 16px;
   height: 16px;
   margin-inline-end: 4px;
   opacity: 0.6;
   flex-shrink: 0;
 }
 
 .event-tooltip-debugger-icon:hover {
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -82,18 +82,16 @@ const {nodeSpec, nodeListSpec, walkerSpe
 
 const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog";
 const FONT_FAMILY_PREVIEW_TEXT_SIZE = 20;
 const PSEUDO_CLASSES = [":hover", ":active", ":focus"];
 const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const IMAGE_FETCHING_TIMEOUT = 500;
-const RX_FUNC_NAME =
-  /((var|const|let)\s+)?([\w$.]+\s*[:=]\s*)*(function)?\s*\*?\s*([\w$]+)?\s*$/;
 
 // The possible completions to a ':' with added score to give certain values
 // some preference.
 const PSEUDO_SELECTORS = [
   [":active", 1],
   [":hover", 1],
   [":focus", 1],
   [":visited", 0],
@@ -428,53 +426,53 @@ var NodeActor = exports.NodeActor = prot
    * Gets event listeners and adds their information to the events array.
    *
    * @param  {Node} node
    *         Node for which we are to get listeners.
    */
   getEventListeners: function (node) {
     let parsers = this._eventParsers;
     let dbg = this.parent().tabActor.makeDebugger();
-    let listeners = [];
-
-    for (let [, {getListeners, normalizeHandler}] of parsers) {
+    let listenerArray = [];
+
+    for (let [, {getListeners, normalizeListener}] of parsers) {
       try {
-        let eventInfos = getListeners(node);
-
-        if (!eventInfos) {
+        let listeners = getListeners(node);
+
+        if (!listeners) {
           continue;
         }
 
-        for (let eventInfo of eventInfos) {
-          if (normalizeHandler) {
-            eventInfo.normalizeHandler = normalizeHandler;
+        for (let listener of listeners) {
+          if (normalizeListener) {
+            listener.normalizeListener = normalizeListener;
           }
 
-          this.processHandlerForEvent(node, listeners, dbg, eventInfo);
+          this.processHandlerForEvent(node, listenerArray, dbg, listener);
         }
       } catch (e) {
         // An object attached to the node looked like a listener but wasn't...
         // do nothing.
       }
     }
 
-    listeners.sort((a, b) => {
+    listenerArray.sort((a, b) => {
       return a.type.localeCompare(b.type);
     });
 
-    return listeners;
+    return listenerArray;
   },
 
   /**
    * Process a handler
    *
    * @param  {Node} node
    *         The node for which we want information.
-   * @param  {Array} events
-   *         The events array contains all event objects that we have gathered
+   * @param  {Array} listenerArray
+   *         listenerArray contains all event objects that we have gathered
    *         so far.
    * @param  {Debugger} dbg
    *         JSDebugger instance.
    * @param  {Object} eventInfo
    *         See event-parsers.js.registerEventParser() for a description of the
    *         eventInfo object.
    *
    * @return {Array}
@@ -483,112 +481,142 @@ var NodeActor = exports.NodeActor = prot
    *             type: "click",
    *             handler: function() { doSomething() },
    *             origin: "http://www.mozilla.com",
    *             searchString: 'onclick="doSomething()"',
    *             tags: tags,
    *             DOM0: true,
    *             capturing: true,
    *             hide: {
-   *               dom0: true
-   *             }
+   *               DOM0: true
+   *             },
+   *             native: false
    *           }
    */
-  processHandlerForEvent: function (node, listeners, dbg, eventInfo) {
-    let type = eventInfo.type || "";
-    let handler = eventInfo.handler;
-    let tags = eventInfo.tags || "";
-    let hide = eventInfo.hide || {};
-    let override = eventInfo.override || {};
+  processHandlerForEvent: function (node, listenerArray, dbg, listener) {
+    let { capturing, handler, normalizeListener } = listener;
+    let dom0 = false;
+    let functionSource = handler.toString();
     let global = Cu.getGlobalForObject(handler);
     let globalDO = dbg.addDebuggee(global);
+    let hide = listener.hide || {};
+    let line = 0;
     let listenerDO = globalDO.makeDebuggeeValue(handler);
-
-    if (eventInfo.normalizeHandler) {
-      listenerDO = eventInfo.normalizeHandler(listenerDO);
+    let native = false;
+    let override = listener.override || {};
+    let tags = listener.tags || "";
+    let type = listener.type || "";
+    let url = "";
+
+    if (normalizeListener) {
+      listenerDO = normalizeListener(listenerDO);
     }
 
     // If the listener is an object with a 'handleEvent' method, use that.
     if (listenerDO.class === "Object" || listenerDO.class === "XULElement") {
       let desc;
 
       while (!desc && listenerDO) {
         desc = listenerDO.getOwnPropertyDescriptor("handleEvent");
         listenerDO = listenerDO.proto;
       }
 
       if (desc && desc.value) {
         listenerDO = desc.value;
       }
     }
 
+    // If the listener is bound to a different context then we need to switch
+    // to the bound function.
     if (listenerDO.isBoundFunction) {
       listenerDO = listenerDO.boundTargetFunction;
     }
 
-    let script = listenerDO.script;
-    let scriptSource = script.source.text;
-    let functionSource =
-      scriptSource.substr(script.sourceStart, script.sourceLength);
-
-    /*
-    The script returned is the whole script and
-    scriptSource.substr(script.sourceStart, script.sourceLength) returns
-    something like this:
-      () { doSomething(); }
-
-    So we need to use some regex magic to get the appropriate function info
-    e.g.:
-      () => { ... }
-      function doit() { ... }
-      doit: function() { ... }
-      es6func() { ... }
-      var|let|const foo = function () { ... }
-      function generator*() { ... }
-    */
-    let scriptBeforeFunc = scriptSource.substr(0, script.sourceStart);
-    let matches = scriptBeforeFunc.match(RX_FUNC_NAME);
-    if (matches && matches.length > 0) {
-      functionSource = matches[0].trim() + functionSource;
+    let { isArrowFunction, name, script, parameterNames } = listenerDO;
+
+    if (script) {
+      let scriptSource = script.source.text;
+
+      // Scripts are provided via script tags. If it wasn't provided by a
+      // script tag it must be a DOM0 event.
+      if (script.source.element) {
+        dom0 = script.source.element.class !== "HTMLScriptElement";
+      } else {
+        dom0 = false;
+      }
+
+      line = script.startLine;
+      url = script.url;
+
+      // Checking for the string "[native code]" is the only way at this point
+      // to check for native code. Even if this provides a false positive then
+      // grabbing the source code a second time is harmless.
+      if (functionSource === "[object Object]" ||
+          functionSource === "[object XULElement]" ||
+          functionSource.includes("[native code]")) {
+        functionSource =
+          scriptSource.substr(script.sourceStart, script.sourceLength);
+
+        // At this point the script looks like this:
+        // () { ... }
+        // We prefix this with "function" if it is not a fat arrow function.
+        if (!isArrowFunction) {
+          functionSource = "function " + functionSource;
+        }
+      }
+    } else {
+      // If the listener is a native one (provided by C++ code) then we have no
+      // access to the script. We use the native flag to prevent showing the
+      // debugger button because the script is not available.
+      native = true;
     }
 
-    let dom0 = false;
-
-    if (typeof node.hasAttribute !== "undefined") {
-      dom0 = !!node.hasAttribute("on" + type);
-    } else {
-      dom0 = !!node["on" + type];
+    // Fat arrow function text always contains the parameters. Function
+    // parameters are often missing e.g. if Array.sort is used as a handler.
+    // If they are missing we provide the parameters ourselves.
+    if (parameterNames && parameterNames.length > 0) {
+      let prefix = "function " + name + "()";
+      let paramString = parameterNames.join(", ");
+
+      if (functionSource.startsWith(prefix)) {
+        functionSource = functionSource.substr(prefix.length);
+
+        functionSource = `function ${name} (${paramString})${functionSource}`;
+      }
     }
 
-    let line = script.startLine;
-    let url = script.url;
-    let origin = url + (dom0 ? "" : ":" + line);
-    let searchString;
-
-    if (dom0) {
-      searchString = "on" + type + "=\"" + script.source.text + "\"";
+    // If the listener is native code we display the filename "[native code]."
+    // This is the official string and should *not* be translated.
+    let origin;
+    if (native) {
+      origin = "[native code]";
     } else {
-      scriptSource = "    " + scriptSource;
+      origin = url + ((dom0 || line === 0) ? "" : ":" + line);
     }
 
     let eventObj = {
-      type: typeof override.type !== "undefined" ? override.type : type,
-      handler: functionSource.trim(),
-      origin: typeof override.origin !== "undefined" ?
-                     override.origin : origin,
-      searchString: typeof override.searchString !== "undefined" ?
-                           override.searchString : searchString,
-      tags: tags,
+      type: override.type || type,
+      handler: override.handler || functionSource.trim(),
+      origin: override.origin || origin,
+      tags: override.tags || tags,
       DOM0: typeof override.dom0 !== "undefined" ? override.dom0 : dom0,
       capturing: typeof override.capturing !== "undefined" ?
-                        override.capturing : eventInfo.capturing,
-      hide: hide
+                 override.capturing : capturing,
+      hide: typeof override.hide !== "undefined" ? override.hide : hide,
+      native
     };
 
-    listeners.push(eventObj);
+    // Hide the debugger icon for DOM0 and native listeners. DOM0 listeners are
+    // generated dynamically from e.g. an onclick="" attribute so the script
+    // doesn't actually exist.
+    if (native || dom0) {
+      eventObj.hide.debugger = true;
+    }
+
+    listenerArray.push(eventObj);
 
     dbg.removeDebuggee(globalDO);
   },
 
   /**
    * Returns a LongStringActor with the node's value.
    */
   getNodeValue: function () {
@@ -638,20 +666,26 @@ var NodeActor = exports.NodeActor = prot
       };
     });
   },
 
   /**
    * Get all event listeners that are listening on this node.
    */
   getEventListenerInfo: function () {
+    let node = this.rawNode;
+
     if (this.rawNode.nodeName.toLowerCase() === "html") {
-      return this.getEventListeners(this.rawNode.ownerGlobal);
+      let winListeners = this.getEventListeners(node.ownerGlobal) || [];
+      let docElementListeners = this.getEventListeners(node) || [];
+      let docListeners = this.getEventListeners(node.parentNode) || [];
+
+      return [...winListeners, ...docElementListeners, ...docListeners];
     }
-    return this.getEventListeners(this.rawNode);
+    return this.getEventListeners(node);
   },
 
   /**
    * Modify a node's attributes.  Passed an array of modifications
    * similar in format to "attributes" mutations.
    * {
    *   attributeName: <string>
    *   attributeNamespace: <optional string>
--- a/devtools/server/event-parsers.js
+++ b/devtools/server/event-parsers.js
@@ -2,45 +2,53 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // This file contains event parsers that are then used by developer tools in
 // order to find information about events affecting an HTML element.
 
 "use strict";
 
-const {Cc, Ci, Cu} = require("chrome");
+const {Cc, Ci} = require("chrome");
+
+// eslint-disable-next-line
+const JQUERY_LIVE_REGEX = /return typeof \w+.*.event\.triggered[\s\S]*\.event\.(dispatch|handle).*arguments/;
 
 loader.lazyGetter(this, "eventListenerService", () => {
   return Cc["@mozilla.org/eventlistenerservice;1"]
            .getService(Ci.nsIEventListenerService);
 });
 
 var parsers = [
   {
     id: "jQuery events",
     getListeners: function (node) {
       let global = node.ownerGlobal.wrappedJSObject;
       let hasJQuery = global.jQuery && global.jQuery.fn && global.jQuery.fn.jquery;
 
       if (!hasJQuery) {
-        return;
+        return undefined;
       }
 
       let jQuery = global.jQuery;
       let handlers = [];
 
       // jQuery 1.2+
       let data = jQuery._data || jQuery.data;
       if (data) {
         let eventsObj = data(node, "events");
         for (let type in eventsObj) {
           let events = eventsObj[type];
           for (let key in events) {
             let event = events[key];
+
+            if (node.wrappedJSObject == global.document && event.selector) {
+              continue;
+            }
+
             if (typeof event === "object" || typeof event === "function") {
               let eventInfo = {
                 type: type,
                 handler: event.handler || event,
                 tags: "jQuery",
                 hide: {
                   capturing: true,
                   dom0: true
@@ -58,16 +66,22 @@ var parsers = [
 
       if (!entry) {
         return handlers;
       }
 
       for (let type in entry.events) {
         let events = entry.events[type];
         for (let key in events) {
+          let event = events[key];
+
+          if (node.wrappedJSObject == global.document && event.selector) {
+            continue;
+          }
+
           if (typeof events[key] === "function") {
             let eventInfo = {
               type: type,
               handler: events[key],
               tags: "jQuery",
               hide: {
                 capturing: true,
                 dom0: true
@@ -85,17 +99,17 @@ var parsers = [
   {
     id: "jQuery live events",
     hasListeners: function (node) {
       return jQueryLiveGetListeners(node, true);
     },
     getListeners: function (node) {
       return jQueryLiveGetListeners(node, false);
     },
-    normalizeHandler: function (handlerDO) {
+    normalizeListener: function (handlerDO) {
       let paths = [
         [".event.proxy/", ".event.proxy/", "*"],
         [".proxy/", "*"]
       ];
 
       let name = handlerDO.displayName;
 
       if (!name) {
@@ -135,17 +149,24 @@ var parsers = [
     }
   },
   {
     id: "DOM events",
     hasListeners: function (node) {
       let listeners;
 
       if (node.nodeName.toLowerCase() === "html") {
-        listeners = eventListenerService.getListenerInfoFor(node.ownerGlobal) || [];
+        let winListeners =
+          eventListenerService.getListenerInfoFor(node.ownerGlobal) || [];
+        let docElementListeners =
+          eventListenerService.getListenerInfoFor(node) || [];
+        let docListeners =
+          eventListenerService.getListenerInfoFor(node.parentNode) || [];
+
+        listeners = [...winListeners, ...docElementListeners, ...docListeners];
       } else {
         listeners = eventListenerService.getListenerInfoFor(node) || [];
       }
 
       for (let listener of listeners) {
         if (listener.listenerObject && listener.type) {
           return true;
         }
@@ -160,17 +181,17 @@ var parsers = [
       // The Node actor's getEventListenerInfo knows that when an html tag has
       // been passed we need the window object so we don't need to account for
       // event hoisting here as we did in hasListeners.
 
       for (let listenerObj of listeners) {
         let listener = listenerObj.listenerObject;
 
         // If there is no JS event listener skip this.
-        if (!listener) {
+        if (!listener || JQUERY_LIVE_REGEX.test(listener.toString())) {
           continue;
         }
 
         let eventInfo = {
           capturing: listenerObj.capturing,
           type: listenerObj.type,
           handler: listener
         };
@@ -183,17 +204,17 @@ var parsers = [
   }
 ];
 
 function jQueryLiveGetListeners(node, boolOnEventFound) {
   let global = node.ownerGlobal.wrappedJSObject;
   let hasJQuery = global.jQuery && global.jQuery.fn && global.jQuery.fn.jquery;
 
   if (!hasJQuery) {
-    return;
+    return undefined;
   }
 
   let jQuery = global.jQuery;
   let handlers = [];
   let data = jQuery._data || jQuery.data;
 
   if (data) {
     // Live events are added to the document and bubble up to all elements.
@@ -230,17 +251,18 @@ function jQueryLiveGetListeners(node, bo
         if (boolOnEventFound && matches) {
           return true;
         }
 
         if (!matches) {
           continue;
         }
 
-        if (!boolOnEventFound && (typeof event === "object" || typeof event === "function")) {
+        if (!boolOnEventFound &&
+            (typeof event === "object" || typeof event === "function")) {
           let eventInfo = {
             type: event.origType || event.type.substr(selector.length + 1),
             handler: event.handler || event,
             tags: "jQuery,Live",
             hide: {
               dom0: true,
               capturing: true
             }
@@ -268,49 +290,51 @@ this.EventParsers = function EventParser
       this.registerEventParser(parserObj);
     }
   }
 };
 
 exports.EventParsers = EventParsers;
 
 EventParsers.prototype = {
-  _eventParsers: new Map(), // NOTE: This is shared amongst all instances.
+  // NOTE: This is shared amongst all instances.
+  _eventParsers: new Map(),
 
   get parsers() {
     return this._eventParsers;
   },
 
   /**
    * Register a new event parser to be used in the processing of event info.
    *
    * @param {Object} parserObj
    *        Each parser must contain the following properties:
    *        - parser, which must take the following form:
    *   {
-   *     id {String}: "jQuery events",         // Unique id.
-   *     getListeners: function(node) { },     // Function that takes a node and
-   *                                           // returns an array of eventInfo
-   *                                           // objects (see below).
+   *     id {String}: "jQuery events",          // Unique id.
+   *     getListeners: function(node) { },      // Function that takes a node
+   *                                            // and returns an array of
+   *                                            // eventInfo objects (see
+   *                                            // below).
    *
-   *     hasListeners: function(node) { },     // Optional function that takes a
-   *                                           // node and returns a boolean
-   *                                           // indicating whether a node has
-   *                                           // listeners attached.
+   *     hasListeners: function(node) { },      // Optional function that takes
+   *                                            // a node and returns a boolean
+   *                                            // indicating whether a node has
+   *                                            // listeners attached.
    *
-   *     normalizeHandler: function(fnDO) { }, // Optional function that takes a
-   *                                           // Debugger.Object instance and
-   *                                           // climbs the scope chain to get
-   *                                           // the function that should be
-   *                                           // displayed in the event bubble
-   *                                           // see the following url for
-   *                                           // details:
-   *                                           //   https://developer.mozilla.org/
-   *                                           //   docs/Tools/Debugger-API/
-   *                                           //   Debugger.Object
+   *     normalizeListener: function(fnDO) { }, // Optional function that takes a
+   *                                            // Debugger.Object instance and
+   *                                            // climbs the scope chain to get
+   *                                            // the function that should be
+   *                                            // displayed in the event bubble
+   *                                            // see the following url for
+   *                                            // details:
+   *                                            //   https://developer.mozilla.org/
+   *                                            //   docs/Tools/Debugger-API/
+   *                                            //   Debugger.Object
    *   }
    *
    * An eventInfo object should take the following form:
    *   {
    *     type {String}:      "click",
    *     handler {Function}: event handler,
    *     tags {String}:      "jQuery,Live", // These tags will be displayed as
    *                                        // attributes in the events popup.
@@ -321,17 +345,17 @@ EventParsers.prototype = {
    *       capturing: false,   // Capturing
    *       dom0: false         // DOM 0
    *     },
    *
    *     override: {                        // The following can be overridden:
    *       type: "click",
    *       origin: "http://www.mozilla.com",
    *       searchString: 'onclick="doSomething()"',
-   *       DOM0: true,
+   *       dom0: true,
    *       capturing: true
    *     }
    *   }
    */
   registerEventParser: function (parserObj) {
     let parserId = parserObj.id;
 
     if (!parserId) {
@@ -339,17 +363,17 @@ EventParsers.prototype = {
     }
     if (this._eventParsers.has(parserId)) {
       throw new Error("Duplicate event parser id " + parserId);
     }
 
     this._eventParsers.set(parserId, {
       getListeners: parserObj.getListeners,
       hasListeners: parserObj.hasListeners,
-      normalizeHandler: parserObj.normalizeHandler
+      normalizeListener: parserObj.normalizeListener
     });
   },
 
   /**
    * Removes parser that matches a given parserId.
    *
    * @param {String} parserId
    *        id of the event parser to unregister.
--- a/toolkit/content/license.html
+++ b/toolkit/content/license.html
@@ -79,16 +79,17 @@
       <li><a href="about:license#arm">ARM License</a></li>
       <li><a href="about:license#bspatch">bspatch License</a></li>
       <li><a href="about:license#cairo">Cairo Component Licenses</a></li>
       <li><a href="about:license#chromium">Chromium License</a></li>
       <li><a href="about:license#codemirror">CodeMirror License</a></li>
       <li><a href="about:license#cubic-bezier">cubic-bezier License</a></li>
       <li><a href="about:license#d3">D3 License</a></li>
       <li><a href="about:license#dagre-d3">Dagre-D3 License</a></li>
+      <li><a href="about:license#diff">diff License</a></li>
       <li><a href="about:license#dtoa">dtoa License</a></li>
       <li><a href="about:license#hunspell-nl">Dutch Spellchecking Dictionary License</a></li>
 #if defined(XP_WIN) || defined(XP_LINUX)
       <li><a href="about:license#emojione">EmojiOne License</a></li>
 #endif
       <li><a href="about:license#hunspell-ee">Estonian Spellchecking Dictionary License</a></li>
       <li><a href="about:license#expat">Expat License</a></li>
       <li><a href="about:license#firebug">Firebug License</a></li>
@@ -2836,22 +2837,52 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO TH
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 </pre>
 
 
-    <hr>
-
-    <h1><a id="dtoa"></a>dtoa License</h1>
-
-    <p>This license applies to the file
-    <span class="path">nsprpub/pr/src/misc/dtoa.c</span>.</p>
+<hr>
+
+<h1><a id="diff"></a>diff License</h1>
+
+<p>This license applies to the file
+<span class="path">devtools/client/inspector/markup/test/helper_diff.js</span>.</p>
+
+<pre>
+Copyright (c) 2014 Slava
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+</pre>
+
+
+<hr>
+
+<h1><a id="dtoa"></a>dtoa License</h1>
+
+<p>This license applies to the file
+<span class="path">nsprpub/pr/src/misc/dtoa.c</span>.</p>
 
 <pre>
 The author of this software is David M. Gay.
 
 Copyright (c) 1991, 2000, 2001 by Lucent Technologies.
 
 Permission to use, copy, modify, and distribute this software for any
 purpose without fee is hereby granted, provided that this entire notice