Web Inspector: WebInspector.Object.addEventListener is O(n), make it O(1)
authornvasilyev@apple.com <nvasilyev@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 Jan 2016 20:20:53 +0000 (20:20 +0000)
committernvasilyev@apple.com <nvasilyev@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 19 Jan 2016 20:20:53 +0000 (20:20 +0000)
https://bugs.webkit.org/show_bug.cgi?id=152422
<rdar://problem/24038047>

Reviewed by Timothy Hatcher.

Source/WebInspectorUI:

Slow addEventListener was the main cause of Console sluggishness[1].

This patch changes:
addEventListener from O(n) to O(1)
removeEventListener from O(n) to O(1)

Now, addEventListener and removeEventListener take <1ms regardless of the
number of listeners attached.

removeEventListener(null, null, thisObject), a special case when all events
for thisObject are removed, was improved from O(n^2) to O(n).

* UserInterface/Base/LinkedList.js: Added.
(LinkedList):
(LinkedList.prototype.clear):
(LinkedList.prototype.get last):
(LinkedList.prototype.push):
(LinkedList.prototype.remove):
(LinkedList.prototype.forEach):
(LinkedList.prototype.toArray):
(LinkedList.prototype.toJSON):
(LinkedListNode):
LinkedList ensures O(1) time complexity for push and remove operations.

* UserInterface/Base/ListMultimap.js: Added.
(ListMultimap):
(ListMultimap.prototype.get size):
(ListMultimap.prototype.add):
(ListMultimap.prototype.delete):
(ListMultimap.prototype.deleteAll):
(ListMultimap.prototype.has):
(ListMultimap.prototype.clear):
(ListMultimap.prototype.forEach):
(ListMultimap.prototype.toArray):
(ListMultimap.prototype.toJSON):
ListMultimap unsures O(1) time complexity for add, has and delete operations.
ListMultimap preserves insertion order by using a LinkedList.

* UserInterface/Base/Object.js:
(WebInspector.Object):
(WebInspector.Object.addEventListener):
(WebInspector.Object.removeEventListener):
(WebInspector.Object.hasEventListeners):
(WebInspector.Object.retainedObjectsWithPrototype):
(WebInspector.Object.prototype.dispatchEventToListeners):
Replace this._listeners[eventType] from array of objects to ListMultimap.

* UserInterface/Main.html:
* UserInterface/Test.html:
* UserInterface/TestStub.html:

LayoutTests:

* inspector/console/console-api-expected.txt:
* inspector/console/console-table-expected.txt:
* inspector/model/remote-object-expected.txt:
Rebaseline tests, add "_listeners: null" to all WebInspector.Object instances.

* inspector/unit-tests/linked-list-expected.txt: Added.
* inspector/unit-tests/linked-list.html: Added.
* inspector/unit-tests/list-multimap-expected.txt: Added.
* inspector/unit-tests/list-multimap.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@195305 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/console/console-api-expected.txt
LayoutTests/inspector/console/console-table-expected.txt
LayoutTests/inspector/model/remote-object-expected.txt
LayoutTests/inspector/unit-tests/linked-list-expected.txt [new file with mode: 0644]
LayoutTests/inspector/unit-tests/linked-list.html [new file with mode: 0644]
LayoutTests/inspector/unit-tests/list-multimap-expected.txt [new file with mode: 0644]
LayoutTests/inspector/unit-tests/list-multimap.html [new file with mode: 0644]
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Base/LinkedList.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Base/ListMultimap.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Base/Object.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Test.html
Source/WebInspectorUI/UserInterface/TestStub.html

index 938b50a..7bf5bb5 100644 (file)
@@ -1,3 +1,21 @@
+2016-01-19  Nikita Vasilyev  <nvasilyev@apple.com>
+
+        Web Inspector: WebInspector.Object.addEventListener is O(n), make it O(1)
+        https://bugs.webkit.org/show_bug.cgi?id=152422
+        <rdar://problem/24038047>
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/console/console-api-expected.txt:
+        * inspector/console/console-table-expected.txt:
+        * inspector/model/remote-object-expected.txt:
+        Rebaseline tests, add "_listeners: null" to all WebInspector.Object instances.
+
+        * inspector/unit-tests/linked-list-expected.txt: Added.
+        * inspector/unit-tests/linked-list.html: Added.
+        * inspector/unit-tests/list-multimap-expected.txt: Added.
+        * inspector/unit-tests/list-multimap.html: Added.
+
 2016-01-19  Brady Eidson  <beidson@apple.com>
 
         Modern IDB: Split all storage/indexeddb/mozilla tests into separate HTML + JS format.
index ba01cc2..091276f 100644 (file)
@@ -19,6 +19,7 @@ CONSOLE MESSAGE: collapsedGroupName
 
 STEP: console.log('console.log')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "console.log",
@@ -41,6 +42,7 @@ STEP: console.log('console.log')
 
 STEP: console.warn('console.warn')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "warning",
   "_messageText": "console.warn",
@@ -63,6 +65,7 @@ STEP: console.warn('console.warn')
 
 STEP: console.error('console.error')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "error",
   "_messageText": "console.error",
@@ -85,6 +88,7 @@ STEP: console.error('console.error')
 
 STEP: console.debug('console.debug')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "console.debug",
@@ -107,6 +111,7 @@ STEP: console.debug('console.debug')
 
 STEP: console.info('console.info')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "info",
   "_messageText": "console.info",
@@ -129,6 +134,7 @@ STEP: console.info('console.info')
 
 STEP: console.assert(false, 'assertion message')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "error",
   "_messageText": "assertion message",
@@ -151,6 +157,7 @@ STEP: console.assert(false, 'assertion message')
 
 STEP: console.trace()
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "",
@@ -165,6 +172,7 @@ STEP: console.trace()
 
 STEP: console.log('string message', string)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "string message",
@@ -193,6 +201,7 @@ STEP: console.log('string message', string)
 
 STEP: console.log('message', object, object)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "message",
@@ -214,12 +223,14 @@ STEP: console.log('message', object, object)
       "_description": "Object",
       "_hasChildren": true,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_description": "Object",
         "_lossless": true,
         "_overflow": false,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "a",
             "_type": "number",
             "_value": "1"
@@ -234,12 +245,14 @@ STEP: console.log('message', object, object)
       "_description": "Object",
       "_hasChildren": true,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_description": "Object",
         "_lossless": true,
         "_overflow": false,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "a",
             "_type": "number",
             "_value": "1"
@@ -255,6 +268,7 @@ STEP: console.log('message', object, object)
 
 STEP: console.error('message', object)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "error",
   "_messageText": "message",
@@ -276,12 +290,14 @@ STEP: console.error('message', object)
       "_description": "Object",
       "_hasChildren": true,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_description": "Object",
         "_lossless": true,
         "_overflow": false,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "a",
             "_type": "number",
             "_value": "1"
@@ -297,6 +313,7 @@ STEP: console.error('message', object)
 
 STEP: console.warn('message', object)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "warning",
   "_messageText": "message",
@@ -318,12 +335,14 @@ STEP: console.warn('message', object)
       "_description": "Object",
       "_hasChildren": true,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_description": "Object",
         "_lossless": true,
         "_overflow": false,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "a",
             "_type": "number",
             "_value": "1"
@@ -339,6 +358,7 @@ STEP: console.warn('message', object)
 
 STEP: console.debug('message', object)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "message",
@@ -360,12 +380,14 @@ STEP: console.debug('message', object)
       "_description": "Object",
       "_hasChildren": true,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_description": "Object",
         "_lossless": true,
         "_overflow": false,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "a",
             "_type": "number",
             "_value": "1"
@@ -381,6 +403,7 @@ STEP: console.debug('message', object)
 
 STEP: console.info('message', object)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "info",
   "_messageText": "message",
@@ -402,12 +425,14 @@ STEP: console.info('message', object)
       "_description": "Object",
       "_hasChildren": true,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_description": "Object",
         "_lossless": true,
         "_overflow": false,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "a",
             "_type": "number",
             "_value": "1"
@@ -423,6 +448,7 @@ STEP: console.info('message', object)
 
 STEP: console.dir(object)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "[object Object]",
@@ -438,12 +464,14 @@ STEP: console.dir(object)
       "_description": "Object",
       "_hasChildren": true,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_description": "Object",
         "_lossless": true,
         "_overflow": false,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "a",
             "_type": "number",
             "_value": "1"
@@ -459,6 +487,7 @@ STEP: console.dir(object)
 
 STEP: console.dirxml(object)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "[object Object]",
@@ -474,12 +503,14 @@ STEP: console.dirxml(object)
       "_description": "Object",
       "_hasChildren": true,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_description": "Object",
         "_lossless": true,
         "_overflow": false,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "a",
             "_type": "number",
             "_value": "1"
@@ -495,6 +526,7 @@ STEP: console.dirxml(object)
 
 STEP: console.group('groupName')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "groupName",
@@ -517,6 +549,7 @@ STEP: console.group('groupName')
 
 STEP: console.groupEnd('groupName')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "groupName",
@@ -539,6 +572,7 @@ STEP: console.groupEnd('groupName')
 
 STEP: console.groupCollapsed('collapsedGroupName')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "collapsedGroupName",
@@ -561,6 +595,7 @@ STEP: console.groupCollapsed('collapsedGroupName')
 
 STEP: console.groupEnd('collapsedGroupName')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "collapsedGroupName",
@@ -583,6 +618,7 @@ STEP: console.groupEnd('collapsedGroupName')
 
 STEP: console.count()
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "<no label>: 1",
@@ -597,6 +633,7 @@ STEP: console.count()
 
 STEP: console.count()
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "<no label>: 2",
@@ -611,6 +648,7 @@ STEP: console.count()
 
 STEP: console.count('')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "<no label>: 3",
@@ -625,6 +663,7 @@ STEP: console.count('')
 
 STEP: console.count('    ')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "    : 1",
@@ -639,6 +678,7 @@ STEP: console.count('    ')
 
 STEP: console.count('')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "<no label>: 4",
@@ -653,6 +693,7 @@ STEP: console.count('')
 
 STEP: console.count('    ')
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "    : 2",
@@ -667,6 +708,7 @@ STEP: console.count('    ')
 
 STEP: console.count(string)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "string: 1",
@@ -681,6 +723,7 @@ STEP: console.count(string)
 
 STEP: console.count(string)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "string: 2",
@@ -695,6 +738,7 @@ STEP: console.count(string)
 
 STEP: console.count(object)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "[object Object]: 1",
@@ -709,6 +753,7 @@ STEP: console.count(object)
 
 STEP: console.count(object)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "[object Object]: 2",
@@ -723,6 +768,7 @@ STEP: console.count(object)
 
 STEP: console.count(otherObject)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "[object Object]: 3",
@@ -737,6 +783,7 @@ STEP: console.count(otherObject)
 
 STEP: console.count(otherObject)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "[object Object]: 4",
@@ -751,6 +798,7 @@ STEP: console.count(otherObject)
 
 STEP: console.count(number)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "42: 1",
@@ -765,6 +813,7 @@ STEP: console.count(number)
 
 STEP: console.count(number)
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "debug",
   "_messageText": "42: 2",
index 0bb2a90..715e56d 100644 (file)
@@ -7,6 +7,7 @@ CONSOLE MESSAGE: line 3: [object Object],[object Object],[object Object]
 
 STEP: console.table([])
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "",
@@ -24,6 +25,7 @@ STEP: console.table([])
       "_hasChildren": true,
       "_size": 0,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_subtype": "array",
         "_description": "Array",
@@ -41,6 +43,7 @@ STEP: console.table([])
 
 STEP: console.table(['apple', 'orange', 'banana'])
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "apple,orange,banana",
@@ -58,6 +61,7 @@ STEP: console.table(['apple', 'orange', 'banana'])
       "_hasChildren": true,
       "_size": 3,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_subtype": "array",
         "_description": "Array",
@@ -66,16 +70,19 @@ STEP: console.table(['apple', 'orange', 'banana'])
         "_size": 3,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "0",
             "_type": "string",
             "_value": "apple"
           },
           {
+            "_listeners": null,
             "_name": "1",
             "_type": "string",
             "_value": "orange"
           },
           {
+            "_listeners": null,
             "_name": "2",
             "_type": "string",
             "_value": "banana"
@@ -91,6 +98,7 @@ STEP: console.table(['apple', 'orange', 'banana'])
 
 STEP: console.table({firstName: 'John', lastName: 'Smith'})
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "[object Object]",
@@ -106,17 +114,20 @@ STEP: console.table({firstName: 'John', lastName: 'Smith'})
       "_description": "Object",
       "_hasChildren": true,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_description": "Object",
         "_lossless": true,
         "_overflow": false,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "firstName",
             "_type": "string",
             "_value": "John"
           },
           {
+            "_listeners": null,
             "_name": "lastName",
             "_type": "string",
             "_value": "Smith"
@@ -132,6 +143,7 @@ STEP: console.table({firstName: 'John', lastName: 'Smith'})
 
 STEP: console.table([['John', 'Smith'], ['Jane', 'Doe'], ['Emily', 'Jones']])
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "John,Smith,Jane,Doe,Emily,Jones",
@@ -149,6 +161,7 @@ STEP: console.table([['John', 'Smith'], ['Jane', 'Doe'], ['Emily', 'Jones']])
       "_hasChildren": true,
       "_size": 3,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_subtype": "array",
         "_description": "Array",
@@ -157,10 +170,12 @@ STEP: console.table([['John', 'Smith'], ['Jane', 'Doe'], ['Emily', 'Jones']])
         "_size": 3,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "0",
             "_type": "object",
             "_subtype": "array",
             "_valuePreview": {
+              "_listeners": null,
               "_type": "object",
               "_subtype": "array",
               "_description": "Array",
@@ -169,11 +184,13 @@ STEP: console.table([['John', 'Smith'], ['Jane', 'Doe'], ['Emily', 'Jones']])
               "_size": 2,
               "_properties": [
                 {
+                  "_listeners": null,
                   "_name": "0",
                   "_type": "string",
                   "_value": "John"
                 },
                 {
+                  "_listeners": null,
                   "_name": "1",
                   "_type": "string",
                   "_value": "Smith"
@@ -183,10 +200,12 @@ STEP: console.table([['John', 'Smith'], ['Jane', 'Doe'], ['Emily', 'Jones']])
             }
           },
           {
+            "_listeners": null,
             "_name": "1",
             "_type": "object",
             "_subtype": "array",
             "_valuePreview": {
+              "_listeners": null,
               "_type": "object",
               "_subtype": "array",
               "_description": "Array",
@@ -195,11 +214,13 @@ STEP: console.table([['John', 'Smith'], ['Jane', 'Doe'], ['Emily', 'Jones']])
               "_size": 2,
               "_properties": [
                 {
+                  "_listeners": null,
                   "_name": "0",
                   "_type": "string",
                   "_value": "Jane"
                 },
                 {
+                  "_listeners": null,
                   "_name": "1",
                   "_type": "string",
                   "_value": "Doe"
@@ -209,10 +230,12 @@ STEP: console.table([['John', 'Smith'], ['Jane', 'Doe'], ['Emily', 'Jones']])
             }
           },
           {
+            "_listeners": null,
             "_name": "2",
             "_type": "object",
             "_subtype": "array",
             "_valuePreview": {
+              "_listeners": null,
               "_type": "object",
               "_subtype": "array",
               "_description": "Array",
@@ -221,11 +244,13 @@ STEP: console.table([['John', 'Smith'], ['Jane', 'Doe'], ['Emily', 'Jones']])
               "_size": 2,
               "_properties": [
                 {
+                  "_listeners": null,
                   "_name": "0",
                   "_type": "string",
                   "_value": "Emily"
                 },
                 {
+                  "_listeners": null,
                   "_name": "1",
                   "_type": "string",
                   "_value": "Jones"
@@ -245,6 +270,7 @@ STEP: console.table([['John', 'Smith'], ['Jane', 'Doe'], ['Emily', 'Jones']])
 
 STEP: console.table([john, jane, emily])
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "[object Object],[object Object],[object Object]",
@@ -262,6 +288,7 @@ STEP: console.table([john, jane, emily])
       "_hasChildren": true,
       "_size": 3,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_subtype": "array",
         "_description": "Array",
@@ -270,20 +297,24 @@ STEP: console.table([john, jane, emily])
         "_size": 3,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "0",
             "_type": "object",
             "_valuePreview": {
+              "_listeners": null,
               "_type": "object",
               "_description": "Person",
               "_lossless": true,
               "_overflow": false,
               "_properties": [
                 {
+                  "_listeners": null,
                   "_name": "firstName",
                   "_type": "string",
                   "_value": "John"
                 },
                 {
+                  "_listeners": null,
                   "_name": "lastName",
                   "_type": "string",
                   "_value": "Smith"
@@ -293,20 +324,24 @@ STEP: console.table([john, jane, emily])
             }
           },
           {
+            "_listeners": null,
             "_name": "1",
             "_type": "object",
             "_valuePreview": {
+              "_listeners": null,
               "_type": "object",
               "_description": "Person",
               "_lossless": true,
               "_overflow": false,
               "_properties": [
                 {
+                  "_listeners": null,
                   "_name": "firstName",
                   "_type": "string",
                   "_value": "Jane"
                 },
                 {
+                  "_listeners": null,
                   "_name": "lastName",
                   "_type": "string",
                   "_value": "Doe"
@@ -316,20 +351,24 @@ STEP: console.table([john, jane, emily])
             }
           },
           {
+            "_listeners": null,
             "_name": "2",
             "_type": "object",
             "_valuePreview": {
+              "_listeners": null,
               "_type": "object",
               "_description": "Person",
               "_lossless": true,
               "_overflow": false,
               "_properties": [
                 {
+                  "_listeners": null,
                   "_name": "firstName",
                   "_type": "string",
                   "_value": "Emily"
                 },
                 {
+                  "_listeners": null,
                   "_name": "lastName",
                   "_type": "string",
                   "_value": "Jones"
@@ -349,6 +388,7 @@ STEP: console.table([john, jane, emily])
 
 STEP: console.table([john, jane, emily], ['firstName'])
 {
+  "_listeners": null,
   "_source": "console-api",
   "_level": "log",
   "_messageText": "[object Object],[object Object],[object Object]",
@@ -366,6 +406,7 @@ STEP: console.table([john, jane, emily], ['firstName'])
       "_hasChildren": true,
       "_size": 3,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_subtype": "array",
         "_description": "Array",
@@ -374,20 +415,24 @@ STEP: console.table([john, jane, emily], ['firstName'])
         "_size": 3,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "0",
             "_type": "object",
             "_valuePreview": {
+              "_listeners": null,
               "_type": "object",
               "_description": "Person",
               "_lossless": true,
               "_overflow": false,
               "_properties": [
                 {
+                  "_listeners": null,
                   "_name": "firstName",
                   "_type": "string",
                   "_value": "John"
                 },
                 {
+                  "_listeners": null,
                   "_name": "lastName",
                   "_type": "string",
                   "_value": "Smith"
@@ -397,20 +442,24 @@ STEP: console.table([john, jane, emily], ['firstName'])
             }
           },
           {
+            "_listeners": null,
             "_name": "1",
             "_type": "object",
             "_valuePreview": {
+              "_listeners": null,
               "_type": "object",
               "_description": "Person",
               "_lossless": true,
               "_overflow": false,
               "_properties": [
                 {
+                  "_listeners": null,
                   "_name": "firstName",
                   "_type": "string",
                   "_value": "Jane"
                 },
                 {
+                  "_listeners": null,
                   "_name": "lastName",
                   "_type": "string",
                   "_value": "Doe"
@@ -420,20 +469,24 @@ STEP: console.table([john, jane, emily], ['firstName'])
             }
           },
           {
+            "_listeners": null,
             "_name": "2",
             "_type": "object",
             "_valuePreview": {
+              "_listeners": null,
               "_type": "object",
               "_description": "Person",
               "_lossless": true,
               "_overflow": false,
               "_properties": [
                 {
+                  "_listeners": null,
                   "_name": "firstName",
                   "_type": "string",
                   "_value": "Emily"
                 },
                 {
+                  "_listeners": null,
                   "_name": "lastName",
                   "_type": "string",
                   "_value": "Jones"
@@ -454,6 +507,7 @@ STEP: console.table([john, jane, emily], ['firstName'])
       "_hasChildren": true,
       "_size": 1,
       "_preview": {
+        "_listeners": null,
         "_type": "object",
         "_subtype": "array",
         "_description": "Array",
@@ -462,6 +516,7 @@ STEP: console.table([john, jane, emily], ['firstName'])
         "_size": 1,
         "_properties": [
           {
+            "_listeners": null,
             "_name": "0",
             "_type": "string",
             "_value": "firstName"
index 7c1adea..bd62fed 100644 (file)
@@ -263,6 +263,7 @@ EXPRESSION: / /
   "_objectId": "<filtered>",
   "_description": "/ /",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "regexp",
     "_description": "/ /",
@@ -270,6 +271,7 @@ EXPRESSION: / /
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "lastIndex",
         "_type": "number",
         "_value": "0"
@@ -287,6 +289,7 @@ EXPRESSION: /(?:)/
   "_objectId": "<filtered>",
   "_description": "/(?:)/",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "regexp",
     "_description": "/(?:)/",
@@ -294,6 +297,7 @@ EXPRESSION: /(?:)/
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "lastIndex",
         "_type": "number",
         "_value": "0"
@@ -311,6 +315,7 @@ EXPRESSION: /^r(e)g[e]{1,}x+/
   "_objectId": "<filtered>",
   "_description": "/^r(e)g[e]{1,}x+/",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "regexp",
     "_description": "/^r(e)g[e]{1,}x+/",
@@ -318,6 +323,7 @@ EXPRESSION: /^r(e)g[e]{1,}x+/
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "lastIndex",
         "_type": "number",
         "_value": "0"
@@ -335,6 +341,7 @@ EXPRESSION: /^r(e)g[e]{1,}x+/ig
   "_objectId": "<filtered>",
   "_description": "/^r(e)g[e]{1,}x+/gi",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "regexp",
     "_description": "/^r(e)g[e]{1,}x+/gi",
@@ -342,6 +349,7 @@ EXPRESSION: /^r(e)g[e]{1,}x+/ig
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "lastIndex",
         "_type": "number",
         "_value": "0"
@@ -359,6 +367,7 @@ EXPRESSION: new RegExp('')
   "_objectId": "<filtered>",
   "_description": "/(?:)/",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "regexp",
     "_description": "/(?:)/",
@@ -366,6 +375,7 @@ EXPRESSION: new RegExp('')
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "lastIndex",
         "_type": "number",
         "_value": "0"
@@ -383,6 +393,7 @@ EXPRESSION: new RegExp('test', 'i')
   "_objectId": "<filtered>",
   "_description": "/test/i",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "regexp",
     "_description": "/test/i",
@@ -390,6 +401,7 @@ EXPRESSION: new RegExp('test', 'i')
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "lastIndex",
         "_type": "number",
         "_value": "0"
@@ -408,6 +420,7 @@ EXPRESSION: []
   "_description": "Array",
   "_size": 0,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -428,6 +441,7 @@ EXPRESSION: [0, -0, 1, 2]
   "_description": "Array",
   "_size": 4,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -436,21 +450,25 @@ EXPRESSION: [0, -0, 1, 2]
     "_size": 4,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "1",
         "_type": "number",
         "_value": "-0"
       },
       {
+        "_listeners": null,
         "_name": "2",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "3",
         "_type": "number",
         "_value": "2"
@@ -469,6 +487,7 @@ EXPRESSION: [[1],[2],[3]]
   "_description": "Array",
   "_size": 3,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -477,10 +496,12 @@ EXPRESSION: [[1],[2],[3]]
     "_size": 3,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "object",
         "_subtype": "array",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -489,6 +510,7 @@ EXPRESSION: [[1],[2],[3]]
           "_size": 1,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "1"
@@ -498,10 +520,12 @@ EXPRESSION: [[1],[2],[3]]
         }
       },
       {
+        "_listeners": null,
         "_name": "1",
         "_type": "object",
         "_subtype": "array",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -510,6 +534,7 @@ EXPRESSION: [[1],[2],[3]]
           "_size": 1,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "2"
@@ -519,10 +544,12 @@ EXPRESSION: [[1],[2],[3]]
         }
       },
       {
+        "_listeners": null,
         "_name": "2",
         "_type": "object",
         "_subtype": "array",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -531,6 +558,7 @@ EXPRESSION: [[1],[2],[3]]
           "_size": 1,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "3"
@@ -553,6 +581,7 @@ EXPRESSION: [true, 1, 1.234, 'string', /regex/]
   "_description": "Array",
   "_size": 5,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -561,26 +590,31 @@ EXPRESSION: [true, 1, 1.234, 'string', /regex/]
     "_size": 5,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "boolean",
         "_value": "true"
       },
       {
+        "_listeners": null,
         "_name": "1",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "2",
         "_type": "number",
         "_value": "1.234"
       },
       {
+        "_listeners": null,
         "_name": "3",
         "_type": "string",
         "_value": "string"
       },
       {
+        "_listeners": null,
         "_name": "4",
         "_type": "object",
         "_subtype": "regexp",
@@ -600,6 +634,7 @@ EXPRESSION: [[null]]
   "_description": "Array",
   "_size": 1,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -608,10 +643,12 @@ EXPRESSION: [[null]]
     "_size": 1,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "object",
         "_subtype": "array",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -620,6 +657,7 @@ EXPRESSION: [[null]]
           "_size": 1,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "object",
               "_subtype": "null",
@@ -643,6 +681,7 @@ EXPRESSION: [[undefined]]
   "_description": "Array",
   "_size": 1,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -651,10 +690,12 @@ EXPRESSION: [[undefined]]
     "_size": 1,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "object",
         "_subtype": "array",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -663,6 +704,7 @@ EXPRESSION: [[undefined]]
           "_size": 1,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "undefined",
               "_value": "undefined"
@@ -685,6 +727,7 @@ EXPRESSION: [{a:1}, {b:2}, {c:2}]
   "_description": "Array",
   "_size": 3,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -693,15 +736,18 @@ EXPRESSION: [{a:1}, {b:2}, {c:2}]
     "_size": 3,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "object",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_description": "Object",
           "_lossless": true,
           "_overflow": false,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "a",
               "_type": "number",
               "_value": "1"
@@ -711,15 +757,18 @@ EXPRESSION: [{a:1}, {b:2}, {c:2}]
         }
       },
       {
+        "_listeners": null,
         "_name": "1",
         "_type": "object",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_description": "Object",
           "_lossless": true,
           "_overflow": false,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "b",
               "_type": "number",
               "_value": "2"
@@ -729,15 +778,18 @@ EXPRESSION: [{a:1}, {b:2}, {c:2}]
         }
       },
       {
+        "_listeners": null,
         "_name": "2",
         "_type": "object",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_description": "Object",
           "_lossless": true,
           "_overflow": false,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "c",
               "_type": "number",
               "_value": "2"
@@ -760,6 +812,7 @@ EXPRESSION: [[{a:1}, {b:2}, {c:2}]]
   "_description": "Array",
   "_size": 1,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -768,10 +821,12 @@ EXPRESSION: [[{a:1}, {b:2}, {c:2}]]
     "_size": 1,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "object",
         "_subtype": "array",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -780,15 +835,18 @@ EXPRESSION: [[{a:1}, {b:2}, {c:2}]]
           "_size": 3,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "object",
               "_valuePreview": {
+                "_listeners": null,
                 "_type": "object",
                 "_description": "Object",
                 "_lossless": true,
                 "_overflow": false,
                 "_properties": [
                   {
+                    "_listeners": null,
                     "_name": "a",
                     "_type": "number",
                     "_value": "1"
@@ -798,15 +856,18 @@ EXPRESSION: [[{a:1}, {b:2}, {c:2}]]
               }
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "object",
               "_valuePreview": {
+                "_listeners": null,
                 "_type": "object",
                 "_description": "Object",
                 "_lossless": true,
                 "_overflow": false,
                 "_properties": [
                   {
+                    "_listeners": null,
                     "_name": "b",
                     "_type": "number",
                     "_value": "2"
@@ -816,15 +877,18 @@ EXPRESSION: [[{a:1}, {b:2}, {c:2}]]
               }
             },
             {
+              "_listeners": null,
               "_name": "2",
               "_type": "object",
               "_valuePreview": {
+                "_listeners": null,
                 "_type": "object",
                 "_description": "Object",
                 "_lossless": true,
                 "_overflow": false,
                 "_properties": [
                   {
+                    "_listeners": null,
                     "_name": "c",
                     "_type": "number",
                     "_value": "2"
@@ -851,6 +915,7 @@ EXPRESSION: arr = []; arr[0] = arr; arr
   "_description": "Array",
   "_size": 1,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -859,6 +924,7 @@ EXPRESSION: arr = []; arr[0] = arr; arr
     "_size": 1,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "object",
         "_subtype": "array",
@@ -878,6 +944,7 @@ EXPRESSION: arr = []; arr.length = 100; arr
   "_description": "Array",
   "_size": 100,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -898,6 +965,7 @@ EXPRESSION: arr = []; arr.length = 100; arr.fill(1)
   "_description": "Array",
   "_size": 100,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -906,51 +974,61 @@ EXPRESSION: arr = []; arr.length = 100; arr.fill(1)
     "_size": 100,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "1",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "2",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "3",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "4",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "5",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "6",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "7",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "8",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "9",
         "_type": "number",
         "_value": "1"
@@ -969,6 +1047,7 @@ EXPRESSION: arr = []; arr.length = 100; arr[10] = 1; arr
   "_description": "Array",
   "_size": 100,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -977,6 +1056,7 @@ EXPRESSION: arr = []; arr.length = 100; arr[10] = 1; arr
     "_size": 100,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "10",
         "_type": "number",
         "_value": "1"
@@ -995,6 +1075,7 @@ EXPRESSION: a = null; (function() { a = arguments; })(1, '2', /3/); a
   "_description": "Arguments",
   "_size": 3,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Arguments",
@@ -1003,16 +1084,19 @@ EXPRESSION: a = null; (function() { a = arguments; })(1, '2', /3/); a
     "_size": 3,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "1",
         "_type": "string",
         "_value": "2"
       },
       {
+        "_listeners": null,
         "_name": "2",
         "_type": "object",
         "_subtype": "regexp",
@@ -1032,6 +1116,7 @@ EXPRESSION: new Int32Array(new ArrayBuffer(16))
   "_description": "Int32Array",
   "_size": 4,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Int32Array",
@@ -1040,21 +1125,25 @@ EXPRESSION: new Int32Array(new ArrayBuffer(16))
     "_size": 4,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "1",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "2",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "3",
         "_type": "number",
         "_value": "0"
@@ -1073,6 +1162,7 @@ EXPRESSION: var intArray = new Int32Array(new ArrayBuffer(16)); for (var i = 0;
   "_description": "Int32Array",
   "_size": 4,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Int32Array",
@@ -1081,21 +1171,25 @@ EXPRESSION: var intArray = new Int32Array(new ArrayBuffer(16)); for (var i = 0;
     "_size": 4,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "1",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "2",
         "_type": "number",
         "_value": "2"
       },
       {
+        "_listeners": null,
         "_name": "3",
         "_type": "number",
         "_value": "3"
@@ -1114,6 +1208,7 @@ EXPRESSION: var buffer = new ArrayBuffer(10000000); var int8View = new Int8Array
   "_description": "Int8Array",
   "_size": 10000000,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Int8Array",
@@ -1122,51 +1217,61 @@ EXPRESSION: var buffer = new ArrayBuffer(10000000); var int8View = new Int8Array
     "_size": 10000000,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "1",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "2",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "3",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "4",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "5",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "6",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "7",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "8",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "9",
         "_type": "number",
         "_value": "0"
@@ -1183,6 +1288,7 @@ EXPRESSION: ({})
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Object",
     "_lossless": true,
@@ -1199,12 +1305,14 @@ EXPRESSION: ({a: 1})
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Object",
     "_lossless": true,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "a",
         "_type": "number",
         "_value": "1"
@@ -1221,22 +1329,26 @@ EXPRESSION: ({a: 1, b: 0, c: -0})
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Object",
     "_lossless": true,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "a",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "b",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "c",
         "_type": "number",
         "_value": "-0"
@@ -1253,28 +1365,33 @@ EXPRESSION: ({a: 1, b: "string", c: /regex/, d: Symbol('sym')})
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Object",
     "_lossless": false,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "a",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "b",
         "_type": "string",
         "_value": "string"
       },
       {
+        "_listeners": null,
         "_name": "c",
         "_type": "object",
         "_subtype": "regexp",
         "_value": "/regex/"
       },
       {
+        "_listeners": null,
         "_name": "d",
         "_type": "symbol",
         "_value": "Symbol(sym)"
@@ -1291,17 +1408,20 @@ EXPRESSION: o = {a:1}; o.b = o; o
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Object",
     "_lossless": false,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "a",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "b",
         "_type": "object",
         "_value": "Object"
@@ -1318,26 +1438,31 @@ EXPRESSION: ({a:function a(){}, b:function b(){}, get getter(){}, set setter(v){
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Object",
     "_lossless": false,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "a",
         "_type": "function",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "b",
         "_type": "function",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "getter",
         "_type": "accessor"
       },
       {
+        "_listeners": null,
         "_name": "setter",
         "_type": "accessor"
       }
@@ -1353,6 +1478,7 @@ EXPRESSION: function Foo() {}; new Foo
   "_objectId": "<filtered>",
   "_description": "Foo",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Foo",
     "_lossless": true,
@@ -1369,6 +1495,7 @@ EXPRESSION: var Foo2 = function() {}; new Foo2
   "_objectId": "<filtered>",
   "_description": "Foo2",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Foo2",
     "_lossless": true,
@@ -1385,6 +1512,7 @@ EXPRESSION: var namespace = {}; namespace.Foo3 = function() {}; new namespace.Fo
   "_objectId": "<filtered>",
   "_description": "Foo3",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Foo3",
     "_lossless": true,
@@ -1401,22 +1529,26 @@ EXPRESSION: function Bar() { this._x = 5 }; Bar.prototype = {constructor: Bar, g
   "_objectId": "<filtered>",
   "_description": "Bar",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Bar",
     "_lossless": false,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "_x",
         "_type": "number",
         "_value": "5"
       },
       {
+        "_listeners": null,
         "_name": "constructor",
         "_type": "function",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "x",
         "_type": "accessor"
       }
@@ -1432,12 +1564,14 @@ EXPRESSION: function Bar2() { this._x = 5 }; Bar.prototype = {get x() {return th
   "_objectId": "<filtered>",
   "_description": "Bar2",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Bar2",
     "_lossless": true,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "_x",
         "_type": "number",
         "_value": "5"
@@ -1454,33 +1588,39 @@ EXPRESSION: window.loadEvent
   "_objectId": "<filtered>",
   "_description": "Event",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Event",
     "_lossless": false,
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "clipboardData",
         "_type": "undefined",
         "_value": "undefined"
       },
       {
+        "_listeners": null,
         "_name": "type",
         "_type": "string",
         "_value": "load"
       },
       {
+        "_listeners": null,
         "_name": "target",
         "_type": "object",
         "_subtype": "node",
         "_value": "#document"
       },
       {
+        "_listeners": null,
         "_name": "currentTarget",
         "_type": "object",
         "_value": "Window"
       },
       {
+        "_listeners": null,
         "_name": "eventPhase",
         "_type": "number",
         "_value": "2"
@@ -1497,12 +1637,14 @@ EXPRESSION: new ArrayBuffer(16)
   "_objectId": "<filtered>",
   "_description": "ArrayBuffer",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "ArrayBuffer",
     "_lossless": true,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "byteLength",
         "_type": "number",
         "_value": "16"
@@ -1519,22 +1661,26 @@ EXPRESSION: new DataView(new ArrayBuffer(16))
   "_objectId": "<filtered>",
   "_description": "DataView",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "DataView",
     "_lossless": false,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "byteOffset",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "byteLength",
         "_type": "number",
         "_value": "16"
       },
       {
+        "_listeners": null,
         "_name": "buffer",
         "_type": "object",
         "_value": "ArrayBuffer"
@@ -1551,32 +1697,38 @@ EXPRESSION: obj = {}; obj['prop'] = 1; obj[Symbol()] = 2; obj[Symbol('sym')] = 3
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Object",
     "_lossless": true,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "prop",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "Symbol()",
         "_type": "number",
         "_value": "2"
       },
       {
+        "_listeners": null,
         "_name": "Symbol(sym)",
         "_type": "number",
         "_value": "3"
       },
       {
+        "_listeners": null,
         "_name": "Symbol(sym)",
         "_type": "number",
         "_value": "4"
       },
       {
+        "_listeners": null,
         "_name": "Symbol(Symbol.iterator)",
         "_type": "symbol",
         "_value": "Symbol()"
@@ -1594,6 +1746,7 @@ EXPRESSION: document.body
   "_objectId": "<filtered>",
   "_description": "body",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "node",
     "_description": "body",
@@ -1601,26 +1754,31 @@ EXPRESSION: document.body
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "aLink",
         "_type": "string",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "background",
         "_type": "string",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "bgColor",
         "_type": "string",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "link",
         "_type": "string",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "text",
         "_type": "string",
         "_value": ""
@@ -1638,6 +1796,7 @@ EXPRESSION: div = document.createElement('div'); div.className = 'foo bar'; div
   "_objectId": "<filtered>",
   "_description": "div.foo.bar",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "node",
     "_description": "div.foo.bar",
@@ -1645,26 +1804,31 @@ EXPRESSION: div = document.createElement('div'); div.className = 'foo bar'; div
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "align",
         "_type": "string",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "title",
         "_type": "string",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "lang",
         "_type": "string",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "translate",
         "_type": "boolean",
         "_value": "true"
       },
       {
+        "_listeners": null,
         "_name": "dir",
         "_type": "string",
         "_value": ""
@@ -1682,6 +1846,7 @@ EXPRESSION: span = document.createElement('span'); span.id = 'foo'; span
   "_objectId": "<filtered>",
   "_description": "span#foo",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "node",
     "_description": "span#foo",
@@ -1689,26 +1854,31 @@ EXPRESSION: span = document.createElement('span'); span.id = 'foo'; span
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "title",
         "_type": "string",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "lang",
         "_type": "string",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "translate",
         "_type": "boolean",
         "_value": "true"
       },
       {
+        "_listeners": null,
         "_name": "dir",
         "_type": "string",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "tabIndex",
         "_type": "number",
         "_value": "-1"
@@ -1726,6 +1896,7 @@ EXPRESSION: input = document.createElement('input'); input.type = 'password'; in
   "_objectId": "<filtered>",
   "_description": "input",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "node",
     "_description": "input",
@@ -1733,26 +1904,31 @@ EXPRESSION: input = document.createElement('input'); input.type = 'password'; in
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "selectionStart",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "selectionEnd",
         "_type": "number",
         "_value": "0"
       },
       {
+        "_listeners": null,
         "_name": "selectionDirection",
         "_type": "string",
         "_value": "none"
       },
       {
+        "_listeners": null,
         "_name": "accept",
         "_type": "string",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "alt",
         "_type": "string",
         "_value": ""
@@ -1770,6 +1946,7 @@ EXPRESSION: text = document.createTextNode('text content'); text
   "_objectId": "<filtered>",
   "_description": "#text",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "node",
     "_description": "#text",
@@ -1777,26 +1954,31 @@ EXPRESSION: text = document.createTextNode('text content'); text
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "wholeText",
         "_type": "string",
         "_value": "text content"
       },
       {
+        "_listeners": null,
         "_name": "splitText",
         "_type": "function",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "replaceWholeText",
         "_type": "function",
         "_value": ""
       },
       {
+        "_listeners": null,
         "_name": "data",
         "_type": "string",
         "_value": "text content"
       },
       {
+        "_listeners": null,
         "_name": "length",
         "_type": "number",
         "_value": "12"
@@ -1814,6 +1996,7 @@ EXPRESSION: comment = document.createComment('comment content'); comment
   "_objectId": "<filtered>",
   "_description": "#comment",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "node",
     "_description": "#comment",
@@ -1821,28 +2004,33 @@ EXPRESSION: comment = document.createComment('comment content'); comment
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "data",
         "_type": "string",
         "_value": "comment content"
       },
       {
+        "_listeners": null,
         "_name": "length",
         "_type": "number",
         "_value": "15"
       },
       {
+        "_listeners": null,
         "_name": "previousElementSibling",
         "_type": "object",
         "_subtype": "null",
         "_value": "null"
       },
       {
+        "_listeners": null,
         "_name": "nextElementSibling",
         "_type": "object",
         "_subtype": "null",
         "_value": "null"
       },
       {
+        "_listeners": null,
         "_name": "substringData",
         "_type": "function",
         "_value": ""
@@ -1860,6 +2048,7 @@ EXPRESSION: svgElement = document.createElementNS('http://www.w3.org/2000/svg',
   "_objectId": "<filtered>",
   "_description": "rect.test",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "node",
     "_description": "rect.test",
@@ -1867,26 +2056,31 @@ EXPRESSION: svgElement = document.createElementNS('http://www.w3.org/2000/svg',
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "x",
         "_type": "object",
         "_value": "SVGAnimatedLength"
       },
       {
+        "_listeners": null,
         "_name": "y",
         "_type": "object",
         "_value": "SVGAnimatedLength"
       },
       {
+        "_listeners": null,
         "_name": "width",
         "_type": "object",
         "_value": "SVGAnimatedLength"
       },
       {
+        "_listeners": null,
         "_name": "height",
         "_type": "object",
         "_value": "SVGAnimatedLength"
       },
       {
+        "_listeners": null,
         "_name": "rx",
         "_type": "object",
         "_value": "SVGAnimatedLength"
@@ -1905,6 +2099,7 @@ EXPRESSION: [document.body, div, span, input, text, comment, svgElement]
   "_description": "Array",
   "_size": 7,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -1913,42 +2108,49 @@ EXPRESSION: [document.body, div, span, input, text, comment, svgElement]
     "_size": 7,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "object",
         "_subtype": "node",
         "_value": "<body>"
       },
       {
+        "_listeners": null,
         "_name": "1",
         "_type": "object",
         "_subtype": "node",
         "_value": "<div class=\"foo bar\">"
       },
       {
+        "_listeners": null,
         "_name": "2",
         "_type": "object",
         "_subtype": "node",
         "_value": "<span id=\"foo\">"
       },
       {
+        "_listeners": null,
         "_name": "3",
         "_type": "object",
         "_subtype": "node",
         "_value": "<input type=\"password\">"
       },
       {
+        "_listeners": null,
         "_name": "4",
         "_type": "object",
         "_subtype": "node",
         "_value": "#text \"text content\""
       },
       {
+        "_listeners": null,
         "_name": "5",
         "_type": "object",
         "_subtype": "node",
         "_value": "<!--comment content-->"
       },
       {
+        "_listeners": null,
         "_name": "6",
         "_type": "object",
         "_subtype": "node",
@@ -1968,6 +2170,7 @@ EXPRESSION: document.head.children
   "_description": "HTMLCollection",
   "_size": 3,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "HTMLCollection",
@@ -1976,18 +2179,21 @@ EXPRESSION: document.head.children
     "_size": 3,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "object",
         "_subtype": "node",
         "_value": "<meta>"
       },
       {
+        "_listeners": null,
         "_name": "1",
         "_type": "object",
         "_subtype": "node",
         "_value": "<script>"
       },
       {
+        "_listeners": null,
         "_name": "2",
         "_type": "object",
         "_subtype": "node",
@@ -2007,6 +2213,7 @@ EXPRESSION: document.getElementsByClassName('my-test')
   "_description": "HTMLCollection",
   "_size": 3,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "HTMLCollection",
@@ -2015,18 +2222,21 @@ EXPRESSION: document.getElementsByClassName('my-test')
     "_size": 3,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "object",
         "_subtype": "node",
         "_value": "<p class=\"my-test\">"
       },
       {
+        "_listeners": null,
         "_name": "1",
         "_type": "object",
         "_subtype": "node",
         "_value": "<p class=\"my-test\">"
       },
       {
+        "_listeners": null,
         "_name": "2",
         "_type": "object",
         "_subtype": "node",
@@ -2046,6 +2256,7 @@ EXPRESSION: document.querySelectorAll('.my-test')
   "_description": "NodeList",
   "_size": 3,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "NodeList",
@@ -2054,18 +2265,21 @@ EXPRESSION: document.querySelectorAll('.my-test')
     "_size": 3,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "object",
         "_subtype": "node",
         "_value": "<p class=\"my-test\">"
       },
       {
+        "_listeners": null,
         "_name": "1",
         "_type": "object",
         "_subtype": "node",
         "_value": "<p class=\"my-test\">"
       },
       {
+        "_listeners": null,
         "_name": "2",
         "_type": "object",
         "_subtype": "node",
@@ -2084,6 +2298,7 @@ EXPRESSION: error = null; try { [].x.x; } catch (e) { error = e; }; error
   "_objectId": "<filtered>",
   "_description": "TypeError: undefined is not an object (evaluating '[].x.x')",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "error",
     "_description": "TypeError: undefined is not an object (evaluating '[].x.x')",
@@ -2091,21 +2306,25 @@ EXPRESSION: error = null; try { [].x.x; } catch (e) { error = e; }; error
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "message",
         "_type": "string",
         "_value": "undefined is not an object (evaluating '[].x.x')"
       },
       {
+        "_listeners": null,
         "_name": "line",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "column",
         "_type": "number",
         "_value": "25"
       },
       {
+        "_listeners": null,
         "_name": "stack",
         "_type": "string",
         "_value": "eval code\neval@[native code]\n_evaluateOn\n_evaluateAndWrap\nevaluate"
@@ -2123,6 +2342,7 @@ EXPRESSION: error = null; try { eval('if()'); } catch (e) { error = e; }; error
   "_objectId": "<filtered>",
   "_description": "SyntaxError: Unexpected token ')'",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "error",
     "_description": "SyntaxError: Unexpected token ')'",
@@ -2130,21 +2350,25 @@ EXPRESSION: error = null; try { eval('if()'); } catch (e) { error = e; }; error
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "message",
         "_type": "string",
         "_value": "Unexpected token ')'"
       },
       {
+        "_listeners": null,
         "_name": "line",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "column",
         "_type": "number",
         "_value": "25"
       },
       {
+        "_listeners": null,
         "_name": "stack",
         "_type": "string",
         "_value": "eval@[native code]\neval code\neval@[native code]\n_evaluateOn\n_evaluateAndWrap\nevaluate"
@@ -2162,6 +2386,7 @@ EXPRESSION: error = null; try { document.createTextNode('').splitText(100); } ca
   "_objectId": "<filtered>",
   "_description": "Error: IndexSizeError: DOM Exception 1",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "error",
     "_description": "Error: IndexSizeError: DOM Exception 1",
@@ -2169,26 +2394,31 @@ EXPRESSION: error = null; try { document.createTextNode('').splitText(100); } ca
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "code",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "name",
         "_type": "string",
         "_value": "IndexSizeError"
       },
       {
+        "_listeners": null,
         "_name": "message",
         "_type": "string",
         "_value": "IndexSizeError: DOM Exception 1"
       },
       {
+        "_listeners": null,
         "_name": "line",
         "_type": "number",
         "_value": "1"
       },
       {
+        "_listeners": null,
         "_name": "column",
         "_type": "number",
         "_value": "58"
@@ -2207,6 +2437,7 @@ EXPRESSION: new Map
   "_description": "Map",
   "_size": 0,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "map",
     "_description": "Map",
@@ -2227,6 +2458,7 @@ EXPRESSION: map = new Map; map.set(1, 2); map.set('key', 'value'); map
   "_description": "Map",
   "_size": 2,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "map",
     "_description": "Map",
@@ -2236,7 +2468,9 @@ EXPRESSION: map = new Map; map.set(1, 2); map.set('key', 'value'); map
     "_properties": [],
     "_entries": [
       {
+        "_listeners": null,
         "_key": {
+          "_listeners": null,
           "_type": "number",
           "_description": "1",
           "_lossless": true,
@@ -2245,6 +2479,7 @@ EXPRESSION: map = new Map; map.set(1, 2); map.set('key', 'value'); map
           "_entries": null
         },
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "2",
           "_lossless": true,
@@ -2254,7 +2489,9 @@ EXPRESSION: map = new Map; map.set(1, 2); map.set('key', 'value'); map
         }
       },
       {
+        "_listeners": null,
         "_key": {
+          "_listeners": null,
           "_type": "string",
           "_description": "key",
           "_lossless": true,
@@ -2263,6 +2500,7 @@ EXPRESSION: map = new Map; map.set(1, 2); map.set('key', 'value'); map
           "_entries": null
         },
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "value",
           "_lossless": true,
@@ -2284,6 +2522,7 @@ EXPRESSION: map = new Map; map.set({a:1}, {b:2}); map.set(document.body, [1,2]);
   "_description": "Map",
   "_size": 2,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "map",
     "_description": "Map",
@@ -2293,13 +2532,16 @@ EXPRESSION: map = new Map; map.set({a:1}, {b:2}); map.set(document.body, [1,2]);
     "_properties": [],
     "_entries": [
       {
+        "_listeners": null,
         "_key": {
+          "_listeners": null,
           "_type": "object",
           "_description": "Object",
           "_lossless": true,
           "_overflow": false,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "a",
               "_type": "number",
               "_value": "1"
@@ -2308,12 +2550,14 @@ EXPRESSION: map = new Map; map.set({a:1}, {b:2}); map.set(document.body, [1,2]);
           "_entries": null
         },
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_description": "Object",
           "_lossless": true,
           "_overflow": false,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "b",
               "_type": "number",
               "_value": "2"
@@ -2323,7 +2567,9 @@ EXPRESSION: map = new Map; map.set({a:1}, {b:2}); map.set(document.body, [1,2]);
         }
       },
       {
+        "_listeners": null,
         "_key": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "node",
           "_description": "body",
@@ -2331,26 +2577,31 @@ EXPRESSION: map = new Map; map.set({a:1}, {b:2}); map.set(document.body, [1,2]);
           "_overflow": true,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "aLink",
               "_type": "string",
               "_value": ""
             },
             {
+              "_listeners": null,
               "_name": "background",
               "_type": "string",
               "_value": ""
             },
             {
+              "_listeners": null,
               "_name": "bgColor",
               "_type": "string",
               "_value": ""
             },
             {
+              "_listeners": null,
               "_name": "link",
               "_type": "string",
               "_value": ""
             },
             {
+              "_listeners": null,
               "_name": "text",
               "_type": "string",
               "_value": ""
@@ -2359,6 +2610,7 @@ EXPRESSION: map = new Map; map.set({a:1}, {b:2}); map.set(document.body, [1,2]);
           "_entries": null
         },
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -2367,11 +2619,13 @@ EXPRESSION: map = new Map; map.set({a:1}, {b:2}); map.set(document.body, [1,2]);
           "_size": 2,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "1"
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "number",
               "_value": "2"
@@ -2393,6 +2647,7 @@ EXPRESSION: map = new Map; for (var i = 0; i <= 100; i++) map.set(i, i); map
   "_description": "Map",
   "_size": 101,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "map",
     "_description": "Map",
@@ -2402,7 +2657,9 @@ EXPRESSION: map = new Map; for (var i = 0; i <= 100; i++) map.set(i, i); map
     "_properties": [],
     "_entries": [
       {
+        "_listeners": null,
         "_key": {
+          "_listeners": null,
           "_type": "number",
           "_description": "0",
           "_lossless": true,
@@ -2411,6 +2668,7 @@ EXPRESSION: map = new Map; for (var i = 0; i <= 100; i++) map.set(i, i); map
           "_entries": null
         },
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "0",
           "_lossless": true,
@@ -2420,7 +2678,9 @@ EXPRESSION: map = new Map; for (var i = 0; i <= 100; i++) map.set(i, i); map
         }
       },
       {
+        "_listeners": null,
         "_key": {
+          "_listeners": null,
           "_type": "number",
           "_description": "1",
           "_lossless": true,
@@ -2429,6 +2689,7 @@ EXPRESSION: map = new Map; for (var i = 0; i <= 100; i++) map.set(i, i); map
           "_entries": null
         },
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "1",
           "_lossless": true,
@@ -2438,7 +2699,9 @@ EXPRESSION: map = new Map; for (var i = 0; i <= 100; i++) map.set(i, i); map
         }
       },
       {
+        "_listeners": null,
         "_key": {
+          "_listeners": null,
           "_type": "number",
           "_description": "2",
           "_lossless": true,
@@ -2447,6 +2710,7 @@ EXPRESSION: map = new Map; for (var i = 0; i <= 100; i++) map.set(i, i); map
           "_entries": null
         },
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "2",
           "_lossless": true,
@@ -2456,7 +2720,9 @@ EXPRESSION: map = new Map; for (var i = 0; i <= 100; i++) map.set(i, i); map
         }
       },
       {
+        "_listeners": null,
         "_key": {
+          "_listeners": null,
           "_type": "number",
           "_description": "3",
           "_lossless": true,
@@ -2465,6 +2731,7 @@ EXPRESSION: map = new Map; for (var i = 0; i <= 100; i++) map.set(i, i); map
           "_entries": null
         },
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "3",
           "_lossless": true,
@@ -2474,7 +2741,9 @@ EXPRESSION: map = new Map; for (var i = 0; i <= 100; i++) map.set(i, i); map
         }
       },
       {
+        "_listeners": null,
         "_key": {
+          "_listeners": null,
           "_type": "number",
           "_description": "4",
           "_lossless": true,
@@ -2483,6 +2752,7 @@ EXPRESSION: map = new Map; for (var i = 0; i <= 100; i++) map.set(i, i); map
           "_entries": null
         },
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "4",
           "_lossless": true,
@@ -2504,6 +2774,7 @@ EXPRESSION: map = new Map; map.set(map, map); map
   "_description": "Map",
   "_size": 1,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "map",
     "_description": "Map",
@@ -2513,7 +2784,9 @@ EXPRESSION: map = new Map; map.set(map, map); map
     "_properties": [],
     "_entries": [
       {
+        "_listeners": null,
         "_key": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "map",
           "_description": "Map",
@@ -2524,6 +2797,7 @@ EXPRESSION: map = new Map; map.set(map, map); map
           "_entries": []
         },
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "map",
           "_description": "Map",
@@ -2547,6 +2821,7 @@ EXPRESSION: map = new WeakMap; strongKey = {id:1}; map.set(strongKey, [1,2]); ma
   "_description": "WeakMap",
   "_size": 1,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "weakmap",
     "_description": "WeakMap",
@@ -2556,13 +2831,16 @@ EXPRESSION: map = new WeakMap; strongKey = {id:1}; map.set(strongKey, [1,2]); ma
     "_properties": [],
     "_entries": [
       {
+        "_listeners": null,
         "_key": {
+          "_listeners": null,
           "_type": "object",
           "_description": "Object",
           "_lossless": true,
           "_overflow": false,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "id",
               "_type": "number",
               "_value": "1"
@@ -2571,6 +2849,7 @@ EXPRESSION: map = new WeakMap; strongKey = {id:1}; map.set(strongKey, [1,2]); ma
           "_entries": null
         },
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -2579,11 +2858,13 @@ EXPRESSION: map = new WeakMap; strongKey = {id:1}; map.set(strongKey, [1,2]); ma
           "_size": 2,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "1"
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "number",
               "_value": "2"
@@ -2605,6 +2886,7 @@ EXPRESSION: new Set
   "_description": "Set",
   "_size": 0,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "set",
     "_description": "Set",
@@ -2625,6 +2907,7 @@ EXPRESSION: set = new Set; set.add(1); set.add(2); set.add('key'); set
   "_description": "Set",
   "_size": 3,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "set",
     "_description": "Set",
@@ -2634,7 +2917,9 @@ EXPRESSION: set = new Set; set.add(1); set.add(2); set.add('key'); set
     "_properties": [],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "1",
           "_lossless": true,
@@ -2644,7 +2929,9 @@ EXPRESSION: set = new Set; set.add(1); set.add(2); set.add('key'); set
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "2",
           "_lossless": true,
@@ -2654,7 +2941,9 @@ EXPRESSION: set = new Set; set.add(1); set.add(2); set.add('key'); set
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "key",
           "_lossless": true,
@@ -2676,6 +2965,7 @@ EXPRESSION: set = new Set; set.add({a:1}); set.add(document.body); set.add([1,2]
   "_description": "Set",
   "_size": 3,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "set",
     "_description": "Set",
@@ -2685,13 +2975,16 @@ EXPRESSION: set = new Set; set.add({a:1}); set.add(document.body); set.add([1,2]
     "_properties": [],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_description": "Object",
           "_lossless": true,
           "_overflow": false,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "a",
               "_type": "number",
               "_value": "1"
@@ -2701,7 +2994,9 @@ EXPRESSION: set = new Set; set.add({a:1}); set.add(document.body); set.add([1,2]
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "node",
           "_description": "body",
@@ -2709,26 +3004,31 @@ EXPRESSION: set = new Set; set.add({a:1}); set.add(document.body); set.add([1,2]
           "_overflow": true,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "aLink",
               "_type": "string",
               "_value": ""
             },
             {
+              "_listeners": null,
               "_name": "background",
               "_type": "string",
               "_value": ""
             },
             {
+              "_listeners": null,
               "_name": "bgColor",
               "_type": "string",
               "_value": ""
             },
             {
+              "_listeners": null,
               "_name": "link",
               "_type": "string",
               "_value": ""
             },
             {
+              "_listeners": null,
               "_name": "text",
               "_type": "string",
               "_value": ""
@@ -2738,7 +3038,9 @@ EXPRESSION: set = new Set; set.add({a:1}); set.add(document.body); set.add([1,2]
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -2747,11 +3049,13 @@ EXPRESSION: set = new Set; set.add({a:1}); set.add(document.body); set.add([1,2]
           "_size": 2,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "1"
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "number",
               "_value": "2"
@@ -2773,6 +3077,7 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set
   "_description": "Set",
   "_size": 101,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "set",
     "_description": "Set",
@@ -2782,7 +3087,9 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set
     "_properties": [],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "0",
           "_lossless": true,
@@ -2792,7 +3099,9 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "1",
           "_lossless": true,
@@ -2802,7 +3111,9 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "2",
           "_lossless": true,
@@ -2812,7 +3123,9 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "3",
           "_lossless": true,
@@ -2822,7 +3135,9 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "4",
           "_lossless": true,
@@ -2844,6 +3159,7 @@ EXPRESSION: set = new Set; set.add(set); set
   "_description": "Set",
   "_size": 1,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "set",
     "_description": "Set",
@@ -2853,7 +3169,9 @@ EXPRESSION: set = new Set; set.add(set); set
     "_properties": [],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "set",
           "_description": "Set",
@@ -2877,6 +3195,7 @@ EXPRESSION: set = new WeakSet; strongKey = {id:1}; set.add(strongKey); set
   "_description": "WeakSet",
   "_size": 1,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "weakset",
     "_description": "WeakSet",
@@ -2886,13 +3205,16 @@ EXPRESSION: set = new WeakSet; strongKey = {id:1}; set.add(strongKey); set
     "_properties": [],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_description": "Object",
           "_lossless": true,
           "_overflow": false,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "id",
               "_type": "number",
               "_value": "1"
@@ -2913,6 +3235,7 @@ EXPRESSION: 'a'[Symbol.iterator]()
   "_objectId": "<filtered>",
   "_description": "String Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "String Iterator",
@@ -2920,6 +3243,7 @@ EXPRESSION: 'a'[Symbol.iterator]()
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "string",
         "_type": "string",
         "_value": "a",
@@ -2928,7 +3252,9 @@ EXPRESSION: 'a'[Symbol.iterator]()
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "a",
           "_lossless": true,
@@ -2949,6 +3275,7 @@ EXPRESSION: 'long string'[Symbol.iterator]()
   "_objectId": "<filtered>",
   "_description": "String Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "String Iterator",
@@ -2956,6 +3283,7 @@ EXPRESSION: 'long string'[Symbol.iterator]()
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "string",
         "_type": "string",
         "_value": "long string",
@@ -2964,7 +3292,9 @@ EXPRESSION: 'long string'[Symbol.iterator]()
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "l",
           "_lossless": true,
@@ -2974,7 +3304,9 @@ EXPRESSION: 'long string'[Symbol.iterator]()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "o",
           "_lossless": true,
@@ -2984,7 +3316,9 @@ EXPRESSION: 'long string'[Symbol.iterator]()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "n",
           "_lossless": true,
@@ -2994,7 +3328,9 @@ EXPRESSION: 'long string'[Symbol.iterator]()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "g",
           "_lossless": true,
@@ -3004,7 +3340,9 @@ EXPRESSION: 'long string'[Symbol.iterator]()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": " ",
           "_lossless": true,
@@ -3025,6 +3363,7 @@ EXPRESSION: [][Symbol.iterator]()
   "_objectId": "<filtered>",
   "_description": "Array Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "Array Iterator",
@@ -3032,10 +3371,12 @@ EXPRESSION: [][Symbol.iterator]()
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "array",
         "_type": "object",
         "_subtype": "array",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -3048,6 +3389,7 @@ EXPRESSION: [][Symbol.iterator]()
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "kind",
         "_type": "string",
         "_value": "value",
@@ -3066,6 +3408,7 @@ EXPRESSION: [1][Symbol.iterator]()
   "_objectId": "<filtered>",
   "_description": "Array Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "Array Iterator",
@@ -3073,10 +3416,12 @@ EXPRESSION: [1][Symbol.iterator]()
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "array",
         "_type": "object",
         "_subtype": "array",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -3085,6 +3430,7 @@ EXPRESSION: [1][Symbol.iterator]()
           "_size": 1,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "1"
@@ -3095,6 +3441,7 @@ EXPRESSION: [1][Symbol.iterator]()
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "kind",
         "_type": "string",
         "_value": "value",
@@ -3103,7 +3450,9 @@ EXPRESSION: [1][Symbol.iterator]()
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "1",
           "_lossless": true,
@@ -3124,6 +3473,7 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'][Symbol.iterator]()
   "_objectId": "<filtered>",
   "_description": "Array Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "Array Iterator",
@@ -3131,6 +3481,7 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'][Symbol.iterator]()
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "array",
         "_type": "object",
         "_subtype": "array",
@@ -3138,6 +3489,7 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'][Symbol.iterator]()
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "kind",
         "_type": "string",
         "_value": "value",
@@ -3146,7 +3498,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'][Symbol.iterator]()
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "1",
           "_lossless": true,
@@ -3156,7 +3510,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'][Symbol.iterator]()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "two",
           "_lossless": true,
@@ -3166,7 +3522,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'][Symbol.iterator]()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "3",
           "_lossless": true,
@@ -3176,7 +3534,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'][Symbol.iterator]()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "four",
           "_lossless": true,
@@ -3186,7 +3546,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'][Symbol.iterator]()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "5",
           "_lossless": true,
@@ -3207,6 +3569,7 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].keys()
   "_objectId": "<filtered>",
   "_description": "Array Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "Array Iterator",
@@ -3214,6 +3577,7 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].keys()
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "array",
         "_type": "object",
         "_subtype": "array",
@@ -3221,6 +3585,7 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].keys()
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "kind",
         "_type": "string",
         "_value": "key",
@@ -3229,7 +3594,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].keys()
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "0",
           "_lossless": true,
@@ -3239,7 +3606,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].keys()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "1",
           "_lossless": true,
@@ -3249,7 +3618,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].keys()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "2",
           "_lossless": true,
@@ -3259,7 +3630,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].keys()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "3",
           "_lossless": true,
@@ -3269,7 +3642,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].keys()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "4",
           "_lossless": true,
@@ -3290,6 +3665,7 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].entries()
   "_objectId": "<filtered>",
   "_description": "Array Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "Array Iterator",
@@ -3297,6 +3673,7 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].entries()
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "array",
         "_type": "object",
         "_subtype": "array",
@@ -3304,6 +3681,7 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].entries()
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "kind",
         "_type": "string",
         "_value": "key+value",
@@ -3312,7 +3690,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].entries()
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -3321,11 +3701,13 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].entries()
           "_size": 2,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "0"
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "number",
               "_value": "1"
@@ -3335,7 +3717,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].entries()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -3344,11 +3728,13 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].entries()
           "_size": 2,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "1"
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "string",
               "_value": "two"
@@ -3358,7 +3744,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].entries()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -3367,11 +3755,13 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].entries()
           "_size": 2,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "2"
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "number",
               "_value": "3"
@@ -3381,7 +3771,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].entries()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -3390,11 +3782,13 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].entries()
           "_size": 2,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "3"
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "string",
               "_value": "four"
@@ -3404,7 +3798,9 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].entries()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -3413,11 +3809,13 @@ EXPRESSION: [1, 'two', 3, 'four', 5, 'size'].entries()
           "_size": 2,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "4"
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "number",
               "_value": "5"
@@ -3438,6 +3836,7 @@ EXPRESSION: map = new Map; map.set(1, 2); map.set('key', 'value'); map.values()
   "_objectId": "<filtered>",
   "_description": "Map Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "Map Iterator",
@@ -3445,6 +3844,7 @@ EXPRESSION: map = new Map; map.set(1, 2); map.set('key', 'value'); map.values()
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "map",
         "_type": "object",
         "_subtype": "map",
@@ -3452,6 +3852,7 @@ EXPRESSION: map = new Map; map.set(1, 2); map.set('key', 'value'); map.values()
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "kind",
         "_type": "string",
         "_value": "value",
@@ -3460,7 +3861,9 @@ EXPRESSION: map = new Map; map.set(1, 2); map.set('key', 'value'); map.values()
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "2",
           "_lossless": true,
@@ -3470,7 +3873,9 @@ EXPRESSION: map = new Map; map.set(1, 2); map.set('key', 'value'); map.values()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "value",
           "_lossless": true,
@@ -3491,6 +3896,7 @@ EXPRESSION: map.keys()
   "_objectId": "<filtered>",
   "_description": "Map Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "Map Iterator",
@@ -3498,6 +3904,7 @@ EXPRESSION: map.keys()
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "map",
         "_type": "object",
         "_subtype": "map",
@@ -3505,6 +3912,7 @@ EXPRESSION: map.keys()
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "kind",
         "_type": "string",
         "_value": "key",
@@ -3513,7 +3921,9 @@ EXPRESSION: map.keys()
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "1",
           "_lossless": true,
@@ -3523,7 +3933,9 @@ EXPRESSION: map.keys()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "key",
           "_lossless": true,
@@ -3544,6 +3956,7 @@ EXPRESSION: map.entries()
   "_objectId": "<filtered>",
   "_description": "Map Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "Map Iterator",
@@ -3551,6 +3964,7 @@ EXPRESSION: map.entries()
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "map",
         "_type": "object",
         "_subtype": "map",
@@ -3558,6 +3972,7 @@ EXPRESSION: map.entries()
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "kind",
         "_type": "string",
         "_value": "key+value",
@@ -3566,7 +3981,9 @@ EXPRESSION: map.entries()
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -3575,11 +3992,13 @@ EXPRESSION: map.entries()
           "_size": 2,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "1"
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "number",
               "_value": "2"
@@ -3589,7 +4008,9 @@ EXPRESSION: map.entries()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -3598,11 +4019,13 @@ EXPRESSION: map.entries()
           "_size": 2,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "string",
               "_value": "key"
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "string",
               "_value": "value"
@@ -3623,6 +4046,7 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set.values
   "_objectId": "<filtered>",
   "_description": "Set Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "Set Iterator",
@@ -3630,6 +4054,7 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set.values
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "set",
         "_type": "object",
         "_subtype": "set",
@@ -3637,6 +4062,7 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set.values
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "kind",
         "_type": "string",
         "_value": "value",
@@ -3645,7 +4071,9 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set.values
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "0",
           "_lossless": true,
@@ -3655,7 +4083,9 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set.values
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "1",
           "_lossless": true,
@@ -3665,7 +4095,9 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set.values
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "2",
           "_lossless": true,
@@ -3675,7 +4107,9 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set.values
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "3",
           "_lossless": true,
@@ -3685,7 +4119,9 @@ EXPRESSION: set = new Set; for (var i = 0; i <= 100; i++) set.add(i); set.values
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "4",
           "_lossless": true,
@@ -3706,6 +4142,7 @@ EXPRESSION: map.entries()
   "_objectId": "<filtered>",
   "_description": "Map Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "Map Iterator",
@@ -3713,6 +4150,7 @@ EXPRESSION: map.entries()
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "map",
         "_type": "object",
         "_subtype": "map",
@@ -3720,6 +4158,7 @@ EXPRESSION: map.entries()
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "kind",
         "_type": "string",
         "_value": "key+value",
@@ -3728,7 +4167,9 @@ EXPRESSION: map.entries()
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -3737,11 +4178,13 @@ EXPRESSION: map.entries()
           "_size": 2,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "1"
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "number",
               "_value": "2"
@@ -3751,7 +4194,9 @@ EXPRESSION: map.entries()
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Array",
@@ -3760,11 +4205,13 @@ EXPRESSION: map.entries()
           "_size": 2,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "string",
               "_value": "key"
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "string",
               "_value": "value"
@@ -3785,6 +4232,7 @@ EXPRESSION: x = undefined; (function() { x = arguments; })(1, 'two'); x[Symbol.i
   "_objectId": "<filtered>",
   "_description": "Array Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "Array Iterator",
@@ -3792,10 +4240,12 @@ EXPRESSION: x = undefined; (function() { x = arguments; })(1, 'two'); x[Symbol.i
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "array",
         "_type": "object",
         "_subtype": "array",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_subtype": "array",
           "_description": "Arguments",
@@ -3804,11 +4254,13 @@ EXPRESSION: x = undefined; (function() { x = arguments; })(1, 'two'); x[Symbol.i
           "_size": 2,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "0",
               "_type": "number",
               "_value": "1"
             },
             {
+              "_listeners": null,
               "_name": "1",
               "_type": "string",
               "_value": "two"
@@ -3819,6 +4271,7 @@ EXPRESSION: x = undefined; (function() { x = arguments; })(1, 'two'); x[Symbol.i
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "kind",
         "_type": "string",
         "_value": "value",
@@ -3827,7 +4280,9 @@ EXPRESSION: x = undefined; (function() { x = arguments; })(1, 'two'); x[Symbol.i
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "number",
           "_description": "1",
           "_lossless": true,
@@ -3837,7 +4292,9 @@ EXPRESSION: x = undefined; (function() { x = arguments; })(1, 'two'); x[Symbol.i
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "two",
           "_lossless": true,
@@ -3858,6 +4315,7 @@ EXPRESSION: Reflect.enumerate({a:1, b:2, c:3})
   "_objectId": "<filtered>",
   "_description": "PropertyName Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "PropertyName Iterator",
@@ -3865,25 +4323,30 @@ EXPRESSION: Reflect.enumerate({a:1, b:2, c:3})
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "object",
         "_type": "object",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_description": "Object",
           "_lossless": true,
           "_overflow": false,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "a",
               "_type": "number",
               "_value": "1"
             },
             {
+              "_listeners": null,
               "_name": "b",
               "_type": "number",
               "_value": "2"
             },
             {
+              "_listeners": null,
               "_name": "c",
               "_type": "number",
               "_value": "3"
@@ -3896,7 +4359,9 @@ EXPRESSION: Reflect.enumerate({a:1, b:2, c:3})
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "a",
           "_lossless": true,
@@ -3906,7 +4371,9 @@ EXPRESSION: Reflect.enumerate({a:1, b:2, c:3})
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "b",
           "_lossless": true,
@@ -3916,7 +4383,9 @@ EXPRESSION: Reflect.enumerate({a:1, b:2, c:3})
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "c",
           "_lossless": true,
@@ -3937,6 +4406,7 @@ EXPRESSION: Reflect.enumerate([1, 2, 3, 4, 5, 6, 7])
   "_objectId": "<filtered>",
   "_description": "PropertyName Iterator",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "iterator",
     "_description": "PropertyName Iterator",
@@ -3944,6 +4414,7 @@ EXPRESSION: Reflect.enumerate([1, 2, 3, 4, 5, 6, 7])
     "_overflow": true,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "object",
         "_type": "object",
         "_subtype": "array",
@@ -3953,7 +4424,9 @@ EXPRESSION: Reflect.enumerate([1, 2, 3, 4, 5, 6, 7])
     ],
     "_entries": [
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "0",
           "_lossless": true,
@@ -3963,7 +4436,9 @@ EXPRESSION: Reflect.enumerate([1, 2, 3, 4, 5, 6, 7])
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "1",
           "_lossless": true,
@@ -3973,7 +4448,9 @@ EXPRESSION: Reflect.enumerate([1, 2, 3, 4, 5, 6, 7])
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "2",
           "_lossless": true,
@@ -3983,7 +4460,9 @@ EXPRESSION: Reflect.enumerate([1, 2, 3, 4, 5, 6, 7])
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "3",
           "_lossless": true,
@@ -3993,7 +4472,9 @@ EXPRESSION: Reflect.enumerate([1, 2, 3, 4, 5, 6, 7])
         }
       },
       {
+        "_listeners": null,
         "_value": {
+          "_listeners": null,
           "_type": "string",
           "_description": "4",
           "_lossless": true,
@@ -4013,12 +4494,14 @@ EXPRESSION: new Promise(function(){})
   "_objectId": "<filtered>",
   "_description": "Promise",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Promise",
     "_lossless": true,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "status",
         "_type": "string",
         "_value": "pending",
@@ -4036,18 +4519,21 @@ EXPRESSION: Promise.reject()
   "_objectId": "<filtered>",
   "_description": "Promise",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Promise",
     "_lossless": true,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "status",
         "_type": "string",
         "_value": "rejected",
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "result",
         "_type": "undefined",
         "_value": "undefined",
@@ -4065,18 +4551,21 @@ EXPRESSION: Promise.reject('result')
   "_objectId": "<filtered>",
   "_description": "Promise",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Promise",
     "_lossless": true,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "status",
         "_type": "string",
         "_value": "rejected",
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "result",
         "_type": "string",
         "_value": "result",
@@ -4094,18 +4583,21 @@ EXPRESSION: Promise.resolve()
   "_objectId": "<filtered>",
   "_description": "Promise",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Promise",
     "_lossless": true,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "status",
         "_type": "string",
         "_value": "resolved",
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "result",
         "_type": "undefined",
         "_value": "undefined",
@@ -4123,27 +4615,32 @@ EXPRESSION: Promise.resolve({result:1})
   "_objectId": "<filtered>",
   "_description": "Promise",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Promise",
     "_lossless": true,
     "_overflow": false,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "status",
         "_type": "string",
         "_value": "resolved",
         "_internal": true
       },
       {
+        "_listeners": null,
         "_name": "result",
         "_type": "object",
         "_valuePreview": {
+          "_listeners": null,
           "_type": "object",
           "_description": "Object",
           "_lossless": true,
           "_overflow": false,
           "_properties": [
             {
+              "_listeners": null,
               "_name": "result",
               "_type": "number",
               "_value": "1"
@@ -4205,6 +4702,7 @@ EXPRESSION: [Beta]
   "_description": "Array",
   "_size": 1,
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_subtype": "array",
     "_description": "Array",
@@ -4213,6 +4711,7 @@ EXPRESSION: [Beta]
     "_size": 1,
     "_properties": [
       {
+        "_listeners": null,
         "_name": "0",
         "_type": "function",
         "_subtype": "class",
@@ -4230,6 +4729,7 @@ EXPRESSION: Object.seal({})
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Object",
     "_lossless": true,
@@ -4246,6 +4746,7 @@ EXPRESSION: Object.freeze({})
   "_objectId": "<filtered>",
   "_description": "Object",
   "_preview": {
+    "_listeners": null,
     "_type": "object",
     "_description": "Object",
     "_lossless": true,
diff --git a/LayoutTests/inspector/unit-tests/linked-list-expected.txt b/LayoutTests/inspector/unit-tests/linked-list-expected.txt
new file mode 100644 (file)
index 0000000..865e100
--- /dev/null
@@ -0,0 +1,25 @@
+Testing all methods of LinkedList.
+
+
+== Running test suite: LinkedList
+-- Running test case: Adding items
+0
+[]
+2
+["one","two"]
+3
+["one","two","three"]
+
+-- Running test case: Removing items
+3
+["one","two","three"]
+2
+["one","three"]
+
+-- Running test case: Removing all items
+0
+[]
+
+-- Running test case: Iterating using forEach method
+["one","two","three"]
+
diff --git a/LayoutTests/inspector/unit-tests/linked-list.html b/LayoutTests/inspector/unit-tests/linked-list.html
new file mode 100644 (file)
index 0000000..b073226
--- /dev/null
@@ -0,0 +1,92 @@
+<!doctype html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("LinkedList");
+
+    suite.addTestCase({
+        name: "Adding items",
+        test: function(resolve, reject) {
+            let list = new LinkedList;
+
+            InspectorTest.log(list.length);
+            InspectorTest.log(list);
+
+            list.push("one");
+            list.push("two");
+            InspectorTest.log(list.length);
+            InspectorTest.log(list);
+
+            list.push("three");
+            InspectorTest.log(list.length);
+            InspectorTest.log(list);
+
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "Removing items",
+        test: function(resolve, reject) {
+            let list = new LinkedList;
+
+            list.push("one");
+            let nodeTwo = list.push("two");
+            list.push("three");
+            InspectorTest.log(list.length);
+            InspectorTest.log(list);
+
+            list.remove(nodeTwo);
+            InspectorTest.log(list.length);
+            InspectorTest.log(list);
+
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "Removing all items",
+        test: function(resolve, reject) {
+            let list = new LinkedList;
+
+            list.push("one");
+            list.push("two");
+            list.push("three");
+            list.clear();
+            InspectorTest.log(list.length);
+            InspectorTest.log(list);
+
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "Iterating using forEach method",
+        test: function(resolve, reject) {
+            let list = new LinkedList;
+
+            list.push("one");
+            list.push("two");
+            list.push("three");
+
+            let values = [];
+            list.forEach(function(value) {
+                values.push(value);
+            });
+            InspectorTest.log(values);
+
+            resolve();
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Testing all methods of LinkedList.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/unit-tests/list-multimap-expected.txt b/LayoutTests/inspector/unit-tests/list-multimap-expected.txt
new file mode 100644 (file)
index 0000000..7bf5fed
--- /dev/null
@@ -0,0 +1,56 @@
+Testing all methods of ListMultimap.
+
+
+== Running test suite: ListMultimap
+-- Running test case: Instantiating Multimap
+0
+[]
+
+-- Running test case: Adding unique keys and values
+2
+[["zero","one"],["two","three"]]
+
+-- Running test case: Adding repeating keys and unique values
+2
+[["zero","one"],["zero","two"]]
+
+-- Running test case: Adding unique keys and repeating values
+3
+[["zero","one"],["two","one"],["three","one"]]
+
+-- Running test case: Adding repeating keys and values
+3
+[["zero","one"],["two","one"],["zero","three"]]
+
+-- Running test case: Deleting existing keys and values
+PASS: The key and the value were successfully deleted.
+1
+[[2,3]]
+PASS: The key and the value were successfully deleted.
+0
+[]
+
+-- Running test case: Deleting non-existing keys and non-existing values
+PASS: Nothing was removed.
+PASS: Nothing was removed.
+PASS: Nothing was removed.
+PASS: Nothing was removed.
+PASS: Nothing was removed.
+3
+[[0,1],[2,3],[4,4]]
+
+-- Running test case: Deleting values for given key
+PASS: Nothing was removed.
+3
+[["opossum","badger"],["opossum","raccoon"],["raccoon","opossum"]]
+PASS: Values were removed.
+1
+[["raccoon","opossum"]]
+
+-- Running test case: Deleting all keys and values
+0
+[]
+
+-- Running test case: Iterating using forEach method
+[["Platypus","Sugar glider"],["Quoll","Wallaby"]]
+
diff --git a/LayoutTests/inspector/unit-tests/list-multimap.html b/LayoutTests/inspector/unit-tests/list-multimap.html
new file mode 100644 (file)
index 0000000..bdb1a40
--- /dev/null
@@ -0,0 +1,191 @@
+<!doctype html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("ListMultimap");
+
+    suite.addTestCase({
+        name: "Instantiating Multimap",
+        test: function(resolve, reject) {
+            let multimap = new ListMultimap;
+
+            InspectorTest.log(multimap.size);
+            InspectorTest.log(multimap);
+
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "Adding unique keys and values",
+        test: function(resolve, reject) {
+            let multimap = new ListMultimap;
+
+            multimap.add("zero", "one");
+            multimap.add("two", "three");
+
+            InspectorTest.log(multimap.size);
+            InspectorTest.log(multimap);
+
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "Adding repeating keys and unique values",
+        test: function(resolve, reject) {
+            let multimap = new ListMultimap;
+
+            multimap.add("zero", "one");
+            multimap.add("zero", "two");
+
+            InspectorTest.log(multimap.size);
+            InspectorTest.log(multimap);
+
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "Adding unique keys and repeating values",
+        test: function(resolve, reject) {
+            let multimap = new ListMultimap;
+
+            multimap.add("zero", "one");
+            multimap.add("two", "one");
+            multimap.add("three", "one");
+
+            InspectorTest.log(multimap.size);
+            InspectorTest.log(multimap);
+
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "Adding repeating keys and values",
+        test: function(resolve, reject) {
+            let multimap = new ListMultimap;
+
+            multimap.add("zero", "one");
+            multimap.add("two", "one");
+            multimap.add("zero", "one");
+            multimap.add("zero", "three");
+
+            InspectorTest.log(multimap.size);
+            InspectorTest.log(multimap);
+
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "Deleting existing keys and values",
+        test: function(resolve, reject) {
+            let multimap = new ListMultimap;
+
+            multimap.add(0, 1);
+            multimap.add(2, 3);
+            multimap.add(2, 3);
+
+            InspectorTest.expectThat(multimap.delete(0, 1), "The key and the value were successfully deleted.");
+
+            InspectorTest.log(multimap.size);
+            InspectorTest.log(multimap);
+
+            InspectorTest.expectThat(multimap.delete(2, 3), "The key and the value were successfully deleted.");
+
+            InspectorTest.log(multimap.size);
+            InspectorTest.log(multimap);
+
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "Deleting non-existing keys and non-existing values",
+        test: function(resolve, reject) {
+            let multimap = new ListMultimap;
+
+            multimap.add(0, 1);
+            multimap.add(2, 3);
+            multimap.add(4, 4);
+
+            InspectorTest.expectThat(!multimap.delete(0, 3), "Nothing was removed.");
+            InspectorTest.expectThat(!multimap.delete(2, 1), "Nothing was removed.");
+            InspectorTest.expectThat(!multimap.delete(3, 0), "Nothing was removed.");
+            InspectorTest.expectThat(!multimap.delete(4, 3), "Nothing was removed.");
+            InspectorTest.expectThat(!multimap.delete(0, 4), "Nothing was removed.");
+
+            InspectorTest.log(multimap.size);
+            InspectorTest.log(multimap);
+
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "Deleting values for given key",
+        test: function(resolve, reject) {
+            let multimap = new ListMultimap;
+
+            multimap.add("opossum", "badger");
+            multimap.add("opossum", "raccoon");
+            multimap.add("raccoon", "opossum");
+
+            InspectorTest.expectThat(!multimap.deleteAll("badger"), "Nothing was removed.");
+
+            InspectorTest.log(multimap.size);
+            InspectorTest.log(multimap);
+
+            InspectorTest.expectThat(multimap.deleteAll("opossum"), "Values were removed.");
+
+            InspectorTest.log(multimap.size);
+            InspectorTest.log(multimap);
+
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "Deleting all keys and values",
+        test: function(resolve, reject) {
+            let multimap = new ListMultimap;
+            multimap.add("badger", "raccoon");
+            multimap.clear();
+
+            InspectorTest.log(multimap.size);
+            InspectorTest.log(multimap);
+
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "Iterating using forEach method",
+        test: function(resolve, reject) {
+            let multimap = new ListMultimap;
+            multimap.add("Platypus", "Sugar glider");
+            multimap.add("Quoll", "Wallaby");
+
+            let list = [];
+            multimap.forEach(function(pair) {
+                list.push(pair);
+            });
+            InspectorTest.log(multimap);
+
+            resolve();
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Testing all methods of ListMultimap.</p>
+</body>
+</html>
index 5447940..dcac8ed 100644 (file)
@@ -1,3 +1,62 @@
+2016-01-19  Nikita Vasilyev  <nvasilyev@apple.com>
+
+        Web Inspector: WebInspector.Object.addEventListener is O(n), make it O(1)
+        https://bugs.webkit.org/show_bug.cgi?id=152422
+        <rdar://problem/24038047>
+
+        Reviewed by Timothy Hatcher.
+
+        Slow addEventListener was the main cause of Console sluggishness[1].
+
+        This patch changes:
+        addEventListener from O(n) to O(1)
+        removeEventListener from O(n) to O(1)
+
+        Now, addEventListener and removeEventListener take <1ms regardless of the
+        number of listeners attached.
+
+        removeEventListener(null, null, thisObject), a special case when all events
+        for thisObject are removed, was improved from O(n^2) to O(n).
+
+        * UserInterface/Base/LinkedList.js: Added.
+        (LinkedList):
+        (LinkedList.prototype.clear):
+        (LinkedList.prototype.get last):
+        (LinkedList.prototype.push):
+        (LinkedList.prototype.remove):
+        (LinkedList.prototype.forEach):
+        (LinkedList.prototype.toArray):
+        (LinkedList.prototype.toJSON):
+        (LinkedListNode):
+        LinkedList ensures O(1) time complexity for push and remove operations.
+
+        * UserInterface/Base/ListMultimap.js: Added.
+        (ListMultimap):
+        (ListMultimap.prototype.get size):
+        (ListMultimap.prototype.add):
+        (ListMultimap.prototype.delete):
+        (ListMultimap.prototype.deleteAll):
+        (ListMultimap.prototype.has):
+        (ListMultimap.prototype.clear):
+        (ListMultimap.prototype.forEach):
+        (ListMultimap.prototype.toArray):
+        (ListMultimap.prototype.toJSON):
+        ListMultimap unsures O(1) time complexity for add, has and delete operations.
+        ListMultimap preserves insertion order by using a LinkedList.
+
+        * UserInterface/Base/Object.js:
+        (WebInspector.Object):
+        (WebInspector.Object.addEventListener):
+        (WebInspector.Object.removeEventListener):
+        (WebInspector.Object.hasEventListeners):
+        (WebInspector.Object.retainedObjectsWithPrototype):
+        (WebInspector.Object.prototype.dispatchEventToListeners):
+        Replace this._listeners[eventType] from array of objects to ListMultimap.
+
+        * UserInterface/Main.html:
+        * UserInterface/Test.html:
+        * UserInterface/TestStub.html:
+
 2016-01-19  Matt Baker  <mattbaker@apple.com>
 
         Web Inspector: cleanup TreeOutline class and separate styles from NavigationSidebarPanel
diff --git a/Source/WebInspectorUI/UserInterface/Base/LinkedList.js b/Source/WebInspectorUI/UserInterface/Base/LinkedList.js
new file mode 100644 (file)
index 0000000..f9978af
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+class LinkedList
+{
+    constructor()
+    {
+        this.head = new LinkedListNode;
+        this.head.next = this.head.prev = this.head;
+        this.length = 0;
+    }
+
+    clear()
+    {
+        this.head.next = this.head.prev = this.head;
+        this.length = 0;
+    }
+
+    get last()
+    {
+        return this.head.prev;
+    }
+
+    push(item)
+    {
+        let newNode = new LinkedListNode(item);
+        let last = this.last;
+        let head = this.head;
+
+        last.next = newNode;
+        newNode.next = head;
+        head.prev = newNode;
+        newNode.prev = last;
+
+        this.length++;
+
+        return newNode;
+    }
+
+    remove(node)
+    {
+        if (!node)
+            return false;
+
+        node.prev.next = node.next;
+        node.next.prev = node.prev;
+
+        this.length--;
+        return true;
+    }
+
+    forEach(callback)
+    {
+        let node = this.head;
+        for (let i = 0, length = this.length; i < length; i++) {
+            node = node.next;
+            let returnValue = callback(node.value, i);
+            if (returnValue === false)
+                return;
+        }
+    }
+
+    toArray()
+    {
+        let node = this.head;
+        let i = this.length;
+        let result = new Array(i);
+        while (i--) {
+            node = node.prev;
+            result[i] = node.value;
+        }
+        return result;
+    }
+
+    toJSON()
+    {
+        return this.toArray();
+    }
+}
+
+
+class LinkedListNode
+{
+    constructor(value)
+    {
+        this.value = value;
+        this.prev = null;
+        this.next = null;
+    }
+}
diff --git a/Source/WebInspectorUI/UserInterface/Base/ListMultimap.js b/Source/WebInspectorUI/UserInterface/Base/ListMultimap.js
new file mode 100644 (file)
index 0000000..a415fb6
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+class ListMultimap
+{
+    constructor()
+    {
+        this._insertionOrderedEntries = new LinkedList;
+        this._keyMap = new Map;
+    }
+
+    get size()
+    {
+        return this._insertionOrderedEntries.length;
+    }
+
+    add(key, value)
+    {
+        let nodeMap = this._keyMap.get(key);
+        if (!nodeMap) {
+            nodeMap = new Map;
+            this._keyMap.set(key, nodeMap);
+        }
+
+        let node = nodeMap.get(value);
+        if (!node) {
+            node = this._insertionOrderedEntries.push([key, value]);
+            nodeMap.set(value, node);
+        }
+
+        return this;
+    }
+
+    delete(key, value)
+    {
+        let nodeMap = this._keyMap.get(key);
+        if (!nodeMap)
+            return false;
+
+        let node = nodeMap.get(value);
+        if (!node)
+            return false;
+
+        nodeMap.delete(value);
+        this._insertionOrderedEntries.remove(node);
+        return true;
+    }
+
+    deleteAll(key)
+    {
+        let nodeMap = this._keyMap.get(key);
+        if (!nodeMap)
+            return false;
+
+        let list = this._insertionOrderedEntries;
+        let didDelete = false;
+        nodeMap.forEach(function(node) {
+            list.remove(node);
+            didDelete = true;
+        });
+
+        this._keyMap.delete(key);
+        return didDelete;
+    }
+
+    has(key, value)
+    {
+        let nodeMap = this._keyMap.get(key);
+        if (!nodeMap)
+            return false;
+
+        return nodeMap.has(value);
+    }
+
+    clear()
+    {
+        this._keyMap = new Map;
+        this._insertionOrderedEntries = new LinkedList;
+    }
+
+    forEach(callback)
+    {
+        this._insertionOrderedEntries.forEach(callback);
+    }
+
+    toArray()
+    {
+        return this._insertionOrderedEntries.toArray();
+    }
+
+    toJSON()
+    {
+        return this.toArray();
+    }
+}
index dc0e418..0f35118 100644 (file)
 
 WebInspector.Object = class WebInspectorObject
 {
+    constructor()
+    {
+        this._listeners = null;
+    }
+
     // Static
 
     static addEventListener(eventType, listener, thisObject)
@@ -40,19 +45,15 @@ WebInspector.Object = class WebInspectorObject
             return;
 
         if (!this._listeners)
-            this._listeners = {};
+            this._listeners = new Map();
 
-        var listeners = this._listeners[eventType];
-        if (!listeners)
-            listeners = this._listeners[eventType] = [];
-
-        // Prevent registering multiple times.
-        for (var i = 0; i < listeners.length; ++i) {
-            if (listeners[i].listener === listener && listeners[i].thisObject === thisObject)
-                return;
+        let listenersTable = this._listeners.get(eventType);
+        if (!listenersTable) {
+            listenersTable = new ListMultimap();
+            this._listeners.set(eventType, listenersTable);
         }
 
-        listeners.push({thisObject, listener});
+        listenersTable.add(thisObject, listener);
     }
 
     static singleFireEventListener(eventType, listener, thisObject)
@@ -75,48 +76,52 @@ WebInspector.Object = class WebInspectorObject
         if (!this._listeners)
             return;
 
-        if (!eventType) {
-            for (eventType in this._listeners)
-                this.removeEventListener(eventType, listener, thisObject);
-            return;
-        }
+        if (thisObject && !eventType) {
+            this._listeners.forEach(function(listenersTable) {
+                let listenerPairs = listenersTable.toArray();
+                for (let i = 0, length = listenerPairs.length; i < length; ++i) {
+                    let existingThisObject = listenerPairs[i][0];
+                    if (existingThisObject === thisObject)
+                        listenersTable.deleteAll(existingThisObject);
+                }
+            });
 
-        var listeners = this._listeners[eventType];
-        if (!listeners)
             return;
-
-        for (var i = listeners.length - 1; i >= 0; --i) {
-            if (listener && listeners[i].listener === listener && listeners[i].thisObject === thisObject)
-                listeners.splice(i, 1);
-            else if (!listener && thisObject && listeners[i].thisObject === thisObject)
-                listeners.splice(i, 1);
         }
 
-        if (!listeners.length)
-            delete this._listeners[eventType];
+        let listenersTable = this._listeners.get(eventType);
+        if (!listenersTable || listenersTable.size === 0)
+            return;
 
-        if (!Object.keys(this._listeners).length)
-            delete this._listeners;
+        let didDelete = listenersTable.delete(thisObject, listener);
+        console.assert(didDelete, "removeEventListener cannot remove " + eventType.toString() + " because it doesn't exist.");
     }
 
+    // Only used by tests.
     static hasEventListeners(eventType)
     {
-        if (!this._listeners || !this._listeners[eventType])
+        if (!this._listeners)
             return false;
-        return true;
+
+        let listenersTable = this._listeners.get(eventType);
+        return listenersTable && listenersTable.size > 0;
     }
 
     // This should only be used within regression tests to detect leaks.
     static retainedObjectsWithPrototype(proto)
     {
         let results = new Set;
-        for (let eventType in this._listeners) {
-            let recordsForEvent = this._listeners[eventType];
-            for (let listener of recordsForEvent) {
-                if (listener.thisObject instanceof proto)
-                    results.add(listener.thisObject);
-            }
+
+        if (this._listeners) {
+            this._listeners.forEach(function(listenersTable, eventType) {
+                listenersTable.forEach(function(pair) {
+                    let thisObject = pair[0];
+                    if (thisObject instanceof proto)
+                        results.add(thisObject);
+                });
+            });
         }
+
         return results;
     }
 
@@ -130,23 +135,29 @@ WebInspector.Object = class WebInspectorObject
 
     dispatchEventToListeners(eventType, eventData)
     {
-        var event = new WebInspector.Event(this, eventType, eventData);
+        let event = new WebInspector.Event(this, eventType, eventData);
 
         function dispatch(object)
         {
-            if (!object || !object.hasOwnProperty("_listeners") || event._stoppedPropagation)
+            if (!object || !object._listeners || event._stoppedPropagation)
+                return;
+
+            if (!(object._listeners instanceof Map)) {
+                console.error("object._listeners should be a Map but it isn't.\n`object` is most likely a WebInspector.EventListenerSet.");
                 return;
+            }
 
-            let listenersForThisEvent = object._listeners[eventType];
-            if (!listenersForThisEvent)
+            let listenersTable = object._listeners.get(eventType);
+            if (!listenersTable)
                 return;
 
             // Make a copy with slice so mutations during the loop doesn't affect us.
-            listenersForThisEvent = listenersForThisEvent.slice(0);
+            let listeners = listenersTable.toArray();
 
             // Iterate over the listeners and call them. Stop if stopPropagation is called.
-            for (var i = 0; i < listenersForThisEvent.length; ++i) {
-                listenersForThisEvent[i].listener.call(listenersForThisEvent[i].thisObject, event);
+            for (let i = 0, length = listeners.length; i < length; ++i) {
+                let [thisObject, listener] = listeners[i];
+                listener.call(thisObject, event);
                 if (event._stoppedPropagation)
                     break;
             }
@@ -159,7 +170,7 @@ WebInspector.Object = class WebInspectorObject
         event._stoppedPropagation = false;
 
         // Dispatch to listeners on all constructors up the prototype chain, including the immediate constructor.
-        var constructor = this.constructor;
+        let constructor = this.constructor;
         while (constructor) {
             dispatch(constructor);
 
index a78c5c1..1193114 100644 (file)
     <script src="Base/WebInspector.js"></script>
     <script src="Protocol/InspectorFrontendHostStub.js"></script>
     <script src="Base/Platform.js"></script>
+    <script src="Base/LinkedList.js"></script>
+    <script src="Base/ListMultimap.js"></script>
     <script src="Base/Object.js"></script>
 
     <script src="Base/DOMUtilities.js"></script>
index e2046df..3808bbf 100644 (file)
@@ -36,6 +36,8 @@
     <script src="External/Esprima/esprima.js"></script>
 
     <script src="Base/WebInspector.js"></script>
+    <script src="Base/LinkedList.js"></script>
+    <script src="Base/ListMultimap.js"></script>
     <script src="Base/Object.js"></script>
 
     <script src="Test/TestHarness.js"></script>
index 7b5204d..c341e3c 100644 (file)
@@ -30,6 +30,8 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     These resources should match the order and groups used in Main.html and Test.html.
     -->
     <script src="Base/WebInspector.js"></script>
+    <script src="Base/LinkedList.js"></script>
+    <script src="Base/ListMultimap.js"></script>
     <script src="Base/Object.js"></script>
 
     <script src="Test/TestSuite.js"></script>