Title: [245914] trunk
Revision
245914
Author
[email protected]
Date
2019-05-30 17:12:03 -0700 (Thu, 30 May 2019)

Log Message

Web Inspector: Audit: there should be a default test for WebInspectorAudit.Resources functionality
https://bugs.webkit.org/show_bug.cgi?id=196710
<rdar://problem/49712348>

Reviewed by Joseph Pecoraro.

Source/_javascript_Core:

* inspector/protocol/Audit.json:
Increment Audit version.

Source/WebInspectorUI:

Previously, there was no way to return data from Audit that wasn't a DOM node, a DOM
attribute (which wasn't "shown" anywhere, as it would highlight that attribute on any
returned DOM nodes), or an error string.

In order for Audits to be more flexible with the types of things they test, there needs to
be a way for other types of data to be sent back and displayed in the Audit tab.

This patch makes it so that an Audit result can now contain other keys/values that are all
expected to be JSON serializable. It will take all of the non-"special" (e.g. "domNodes" or
"errors") keys/values and display them as object trees. This way, any JSON serializable data
can be sent with the result and be displayed in the Audit tab.

* UserInterface/Models/AuditTestBase.js:
(WI.AuditTestBase.prototype.async setup):
(WI.AuditTestBase.prototype.clearResult):
* UserInterface/Models/AuditTestGroup.js:
(WI.AuditTestCase.prototype.clearResult):
(WI.AuditTestCase.prototype._updateResult):
* UserInterface/Models/AuditTestCase.js:
(WI.AuditTestCase.prototype.async run):
(WI.AuditTestCase.prototype.async run.async parseResponse):
(WI.AuditTestCase.prototype.async run.async parseResponse.checkResultProperty):
(WI.AuditTestCase.prototype.async run.async parseResponse.checkResultProperty.addErrorForValueType):
(WI.AuditTestCase.prototype.async run.async parseResponse.async resultArrayForEach):
(WI.AuditTestCase.prototype.async run.async parseResponse.inspectedPage_stringify): Added.
Rename `ResultCleared` to `ResultChanged` so that it can (semantically) be used whenever a
new result is set in addition to when an existing one is cleared. This is needed so that
`AuditTestCaseContentView` will refresh each time the result changes, instead of only in the
first `layout()` after the last result was cleared.

* UserInterface/Models/AuditTestCaseResult.js:
(WI.AuditTestCaseResult.async fromPayload):
(WI.AuditTestCaseResult.prototype.toJSON):
Drive-by: fix the check for optional `data` values to still warn if the value is `null`.

* UserInterface/Controllers/AuditManager.js:
(WI.AuditManager.prototype._addDefaultTests):
Add a default test "Demo Audit > Result Data > data-custom" as an example how to write an
Audit that returns custom data, as well as how that custom data is shown in the Audit tab.

* UserInterface/Views/AuditTestContentView.js:
(WI.AuditTestContentView.prototype.shown):
(WI.AuditTestContentView.prototype.handleResultChanged): Added.
* UserInterface/Views/AuditTestCaseContentView.js:
(WI.AuditTestCaseContentView):
(WI.AuditTestCaseContentView.prototype.layout):
(WI.AuditTestCaseContentView.prototype.handleResultChanged): Added.
Preserve the UI for each section across `layout()`s, so that expand/collapse states aren't
reset each time the user changes the selected Audit.

* UserInterface/Views/AuditTestCaseContentView.css:
(.content-view.audit-test-case > section table > tr > td + td): Added.
Drive-by: ensure that the "index" table column is never larger than it needs to be.

* UserInterface/Views/AuditTreeElement.js:
(WI.AuditTreeElement.prototype.onattach):
(WI.AuditTreeElement.prototype._handleTestResultChanged): Added.
(WI.AuditTreeElement.prototype._handleTestResultCleared): Deleted.

* Localizations/en.lproj/localizedStrings.js:

LayoutTests:

* inspector/audit/manager-start-setup.html:
* inspector/audit/manager-start-setup-expected.txt:
* inspector/model/auditTestCase-expected.txt:
* inspector/model/auditTestCaseResult-expected.txt:
* inspector/model/auditTestGroup-expected.txt:
* inspector/unit-tests/object-utilities.html:
* inspector/unit-tests/object-utilities-expected.txt:

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (245913 => 245914)


--- trunk/LayoutTests/ChangeLog	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/LayoutTests/ChangeLog	2019-05-31 00:12:03 UTC (rev 245914)
@@ -1,3 +1,19 @@
+2019-05-30  Devin Rousso  <[email protected]>
+
+        Web Inspector: Audit: there should be a default test for WebInspectorAudit.Resources functionality
+        https://bugs.webkit.org/show_bug.cgi?id=196710
+        <rdar://problem/49712348>
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/audit/manager-start-setup.html:
+        * inspector/audit/manager-start-setup-expected.txt:
+        * inspector/model/auditTestCase-expected.txt:
+        * inspector/model/auditTestCaseResult-expected.txt:
+        * inspector/model/auditTestGroup-expected.txt:
+        * inspector/unit-tests/object-utilities.html:
+        * inspector/unit-tests/object-utilities-expected.txt:
+
 2019-05-30  Andres Gonzalez  <[email protected]>
 
         Inserting a newline in contenteditable causes two characters to be added instead of one

Modified: trunk/LayoutTests/inspector/audit/manager-start-setup-expected.txt (245913 => 245914)


--- trunk/LayoutTests/inspector/audit/manager-start-setup-expected.txt	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/LayoutTests/inspector/audit/manager-start-setup-expected.txt	2019-05-31 00:12:03 UTC (rev 245914)
@@ -3,18 +3,18 @@
 
 == Running test suite: AuditManager.prototype.start
 -- Running test case: AuditManager.prototype.start.SyncSetup
-PASS: The setup function should have set __test to 42.
+PASS: The setup function should have set test to 42.
 
 -- Running test case: AuditManager.prototype.start.AsyncSetup
-PASS: The setup function should have set __test to 42.
+PASS: The setup function should have set test to 42.
 
 -- Running test case: AuditManager.prototype.start.SubLevelSetup
-PASS: The setup function should have set __test to undefined.
+PASS: The setup function should have set test to undefined.
 
 -- Running test case: AuditManager.prototype.start.OverriddenSetup
-PASS: The setup function should have set __test to B.
+PASS: The setup function should have set test to B.
 
 -- Running test case: AuditManager.prototype.start.MultipleTopLevel
-PASS: The setup function should have set __test to A.
-PASS: The setup function should have set __test to B.
+PASS: The setup function should have set test to A.
+PASS: The setup function should have set test to B.
 

Modified: trunk/LayoutTests/inspector/audit/manager-start-setup.html (245913 => 245914)


--- trunk/LayoutTests/inspector/audit/manager-start-setup.html	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/LayoutTests/inspector/audit/manager-start-setup.html	2019-05-31 00:12:03 UTC (rev 245914)
@@ -7,11 +7,11 @@
 function test()
 {
     const auditTestString = (function() {
-        return {
-            level: "pass",
-            __test: WebInspectorAudit.__test,
-        };
-    }).toString();
+    return {
+        level: "pass",
+        test: WebInspectorAudit.test,
+    };
+}).toString();
 
     async function wrapTest(audit, expected, {getResultCallback} = {}) {
         WI.auditManager._addTest(audit);
@@ -22,8 +22,11 @@
 
         InspectorTest.assert(result.didPass, "The test should pass.");
         InspectorTest.assert(!result.data.errors, "There should be no errors.");
-        InspectorTest.expectEqual(result.data.__test.value, expected, `The setup function should have set __test to ${expected}.`);
+        if (result.data.errors)
+            InspectorTest.json(result.data.errors);
 
+        InspectorTest.expectEqual(result.data.test, expected, `The setup function should have set test to ${expected}.`);
+
         WI.auditManager.removeTest(audit);
     }
 
@@ -34,7 +37,7 @@
         description: "Check that the setup of an audit is actually run.",
         async test() {
             const setup = (function() {
-                WebInspectorAudit.__test = 42;
+                WebInspectorAudit.test = 42;
             }).toString();
 
             let audit = new WI.AuditTestCase("AuditManager.prototype.start.SyncSetup", auditTestString, {setup});
@@ -52,7 +55,7 @@
                     setTimeout(resolve, 10);
                 });
 
-                WebInspectorAudit.__test = 42;
+                WebInspectorAudit.test = 42;
             }).toString();
 
             let audit = new WI.AuditTestCase("AuditManager.prototype.start.AsyncSetup", auditTestString, {setup});
@@ -66,7 +69,7 @@
         description: "Check that the setup of a non-top-level audit is not run.",
         async test() {
             const setup = (function() {
-                WebInspectorAudit.__test = 42;
+                WebInspectorAudit.test = 42;
             }).toString();
 
             let audit = new WI.AuditTestGroup("AuditManager.prototype.start.SubLevelSetup.Group", [
@@ -86,15 +89,15 @@
         description: "Check that only the setup of top-level audits is run.",
         async test() {
             const setupA = (function() {
-                if (!WebInspectorAudit.__test)
-                    WebInspectorAudit.__test = "";
-                WebInspectorAudit.__test += "A";
+                if (!WebInspectorAudit.test)
+                    WebInspectorAudit.test = "";
+                WebInspectorAudit.test += "A";
             }).toString();
 
             const setupB = (function() {
-                if (!WebInspectorAudit.__test)
-                    WebInspectorAudit.__test = "";
-                WebInspectorAudit.__test += "B";
+                if (!WebInspectorAudit.test)
+                    WebInspectorAudit.test = "";
+                WebInspectorAudit.test += "B";
             }).toString();
 
             let audit = new WI.AuditTestGroup("AuditManager.prototype.start.OverriddenLevelSetup.Group", [
@@ -114,9 +117,9 @@
         description: "Test that a new WebInspectorAudit object is created for each setup call.",
         async test() {
             const setupA = (function() {
-                if (!WebInspectorAudit.__test)
-                    WebInspectorAudit.__test = "";
-                WebInspectorAudit.__test += "A";
+                if (!WebInspectorAudit.test)
+                    WebInspectorAudit.test = "";
+                WebInspectorAudit.test += "A";
             }).toString();
 
             let auditA = new WI.AuditTestCase("AuditManager.prototype.start.MultipleTopLevel.A", auditTestString, {setup: setupA});
@@ -124,9 +127,9 @@
             await wrapTest(auditA, "A");
 
             const setupB = (function() {
-                if (!WebInspectorAudit.__test)
-                    WebInspectorAudit.__test = "";
-                WebInspectorAudit.__test += "B";
+                if (!WebInspectorAudit.test)
+                    WebInspectorAudit.test = "";
+                WebInspectorAudit.test += "B";
             }).toString();
 
             let auditB = new WI.AuditTestCase("AuditManager.prototype.start.MultipleTopLevel.B", auditTestString, {setup: setupB});

Modified: trunk/LayoutTests/inspector/model/auditTestCase-expected.txt (245913 => 245914)


--- trunk/LayoutTests/inspector/model/auditTestCase-expected.txt	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/LayoutTests/inspector/model/auditTestCase-expected.txt	2019-05-31 00:12:03 UTC (rev 245914)
@@ -28,7 +28,7 @@
 {
   "type": "test-case",
   "name": "validWithInvalidOptionals test name",
-  "supports": 3,
+  "supports": 4,
   "test": "validWithInvalidOptionals test function"
 }
 
@@ -37,7 +37,7 @@
   "type": "test-case",
   "name": "validWithValidOptionals test name",
   "description": "validWithValidOptionals test description",
-  "supports": 1,
+  "supports": 2,
   "setup": "validWithValidOptionals test setup",
   "test": "validWithValidOptionals test function"
 }

Modified: trunk/LayoutTests/inspector/model/auditTestCaseResult-expected.txt (245913 => 245914)


--- trunk/LayoutTests/inspector/model/auditTestCaseResult-expected.txt	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/LayoutTests/inspector/model/auditTestCaseResult-expected.txt	2019-05-31 00:12:03 UTC (rev 245914)
@@ -32,6 +32,9 @@
 }
 
 -- Running test case: AuditTestCaseResult.fromPayload.validWithInvalidSubOptionals
+WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-array "data.domNodes" value
+WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-array "data.domAttributes" value
+WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-array "data.errors" value
 WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-object "metadata.startTimestamp" value
 WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-object "metadata.asyncTimestamp" value
 WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-object "metadata.endTimestamp" value

Modified: trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt (245913 => 245914)


--- trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt	2019-05-31 00:12:03 UTC (rev 245914)
@@ -43,12 +43,12 @@
 {
   "type": "test-group",
   "name": "validWithInvalidOptionals group name",
-  "supports": 3,
+  "supports": 4,
   "tests": [
     {
       "type": "test-case",
       "name": "validWithInvalidOptionals test name",
-      "supports": 4,
+      "supports": 5,
       "test": "validWithInvalidOptionals test function"
     }
   ]
@@ -59,7 +59,7 @@
   "type": "test-group",
   "name": "validWithValidOptionals group name",
   "description": "validWithValidOptionals group description",
-  "supports": 1,
+  "supports": 2,
   "setup": "validWithValidOptionals group setup",
   "tests": [
     {
@@ -66,7 +66,7 @@
       "type": "test-case",
       "name": "validWithValidOptionals test name",
       "description": "validWithValidOptionals test description",
-      "supports": 0,
+      "supports": 1,
       "setup": "validWithValidOptionals test setup",
       "test": "validWithValidOptionals test function"
     }
@@ -78,7 +78,7 @@
   "type": "test-group",
   "name": "validNested group name",
   "description": "validNested group description",
-  "supports": 1,
+  "supports": 2,
   "setup": "validNested group setup",
   "tests": [
     {
@@ -85,7 +85,7 @@
       "type": "test-group",
       "name": "validNested nested group name",
       "description": "validNested nested group description",
-      "supports": 0,
+      "supports": 1,
       "setup": "validNested nested group setup",
       "tests": [
         {
@@ -92,7 +92,7 @@
           "type": "test-case",
           "name": "validNested nested test name",
           "description": "validNested nested test description",
-          "supports": -1,
+          "supports": 0,
           "setup": "validNested nested test setup",
           "test": "validNested nested test function"
         }
@@ -102,7 +102,7 @@
       "type": "test-case",
       "name": "validNested test name",
       "description": "validNested test description",
-      "supports": -2,
+      "supports": -1,
       "setup": "validNested test setup",
       "test": "validNested test function"
     }

Modified: trunk/LayoutTests/inspector/unit-tests/object-utilities-expected.txt (245913 => 245914)


--- trunk/LayoutTests/inspector/unit-tests/object-utilities-expected.txt	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/LayoutTests/inspector/unit-tests/object-utilities-expected.txt	2019-05-31 00:12:03 UTC (rev 245914)
@@ -22,3 +22,13 @@
 PASS: shallowEqual of objects with different constructors should be false.
 PASS: shallowEqual of objects with different constructors should be false.
 
+-- Running test case: Object.filter
+PASS: filter should remove all entries where the key isn't in ["a","b","c"].
+PASS: filter should remove all entries where the key isn't in ["a"].
+PASS: filter should remove all entries where the key isn't in ["b"].
+PASS: filter should remove all entries where the key isn't in ["c"].
+PASS: filter should remove all entries where the value isn't in [1,2,3].
+PASS: filter should remove all entries where the value isn't in [1].
+PASS: filter should remove all entries where the value isn't in [2].
+PASS: filter should remove all entries where the value isn't in [3].
+

Modified: trunk/LayoutTests/inspector/unit-tests/object-utilities.html (245913 => 245914)


--- trunk/LayoutTests/inspector/unit-tests/object-utilities.html	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/LayoutTests/inspector/unit-tests/object-utilities.html	2019-05-31 00:12:03 UTC (rev 245914)
@@ -51,6 +51,29 @@
         }
     });
 
+    suite.addTestCase({
+        name: "Object.filter",
+        test() {
+            const object = {a: 1, b: 2};
+
+            function checkKey(keys, expected) {
+                InspectorTest.expectShallowEqual(Object.filter(object, (key, value) => keys.includes(key)), expected, `filter should remove all entries where the key isn't in ${JSON.stringify(keys)}.`);
+            }
+            checkKey(["a", "b", "c"], {a: 1, b: 2});
+            checkKey(["a"], {a: 1});
+            checkKey(["b"], {b: 2});
+            checkKey(["c"], {});
+
+            function checkValue(values, expected) {
+                InspectorTest.expectShallowEqual(Object.filter(object, (key, value) => values.includes(value)), expected, `filter should remove all entries where the value isn't in ${JSON.stringify(values)}.`);
+            }
+            checkValue([1, 2, 3], {a: 1, b: 2});
+            checkValue([1], {a: 1});
+            checkValue([2], {b: 2});
+            checkValue([3], {});
+        }
+    });
+
     suite.runTestCasesAndFinish();
 }
 </script>

Modified: trunk/Source/_javascript_Core/ChangeLog (245913 => 245914)


--- trunk/Source/_javascript_Core/ChangeLog	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/_javascript_Core/ChangeLog	2019-05-31 00:12:03 UTC (rev 245914)
@@ -1,5 +1,16 @@
 2019-05-30  Devin Rousso  <[email protected]>
 
+        Web Inspector: Audit: there should be a default test for WebInspectorAudit.Resources functionality
+        https://bugs.webkit.org/show_bug.cgi?id=196710
+        <rdar://problem/49712348>
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/protocol/Audit.json:
+        Increment Audit version.
+
+2019-05-30  Devin Rousso  <[email protected]>
+
         Web Inspector: Audit: tests are unable to get the current Audit version
         https://bugs.webkit.org/show_bug.cgi?id=198270
 

Modified: trunk/Source/_javascript_Core/inspector/protocol/Audit.json (245913 => 245914)


--- trunk/Source/_javascript_Core/inspector/protocol/Audit.json	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/_javascript_Core/inspector/protocol/Audit.json	2019-05-31 00:12:03 UTC (rev 245914)
@@ -1,7 +1,7 @@
 {
     "domain": "Audit",
     "description": "",
-    "version": 2,
+    "version": 3,
     "commands": [
         {
             "name": "setup",

Modified: trunk/Source/WebInspectorUI/ChangeLog (245913 => 245914)


--- trunk/Source/WebInspectorUI/ChangeLog	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/WebInspectorUI/ChangeLog	2019-05-31 00:12:03 UTC (rev 245914)
@@ -1,3 +1,72 @@
+2019-05-30  Devin Rousso  <[email protected]>
+
+        Web Inspector: Audit: there should be a default test for WebInspectorAudit.Resources functionality
+        https://bugs.webkit.org/show_bug.cgi?id=196710
+        <rdar://problem/49712348>
+
+        Reviewed by Joseph Pecoraro.
+
+        Previously, there was no way to return data from Audit that wasn't a DOM node, a DOM
+        attribute (which wasn't "shown" anywhere, as it would highlight that attribute on any
+        returned DOM nodes), or an error string.
+
+        In order for Audits to be more flexible with the types of things they test, there needs to
+        be a way for other types of data to be sent back and displayed in the Audit tab.
+
+        This patch makes it so that an Audit result can now contain other keys/values that are all
+        expected to be JSON serializable. It will take all of the non-"special" (e.g. "domNodes" or
+        "errors") keys/values and display them as object trees. This way, any JSON serializable data
+        can be sent with the result and be displayed in the Audit tab.
+
+        * UserInterface/Models/AuditTestBase.js:
+        (WI.AuditTestBase.prototype.async setup):
+        (WI.AuditTestBase.prototype.clearResult):
+        * UserInterface/Models/AuditTestGroup.js:
+        (WI.AuditTestCase.prototype.clearResult):
+        (WI.AuditTestCase.prototype._updateResult):
+        * UserInterface/Models/AuditTestCase.js:
+        (WI.AuditTestCase.prototype.async run):
+        (WI.AuditTestCase.prototype.async run.async parseResponse):
+        (WI.AuditTestCase.prototype.async run.async parseResponse.checkResultProperty):
+        (WI.AuditTestCase.prototype.async run.async parseResponse.checkResultProperty.addErrorForValueType):
+        (WI.AuditTestCase.prototype.async run.async parseResponse.async resultArrayForEach):
+        (WI.AuditTestCase.prototype.async run.async parseResponse.inspectedPage_stringify): Added.
+        Rename `ResultCleared` to `ResultChanged` so that it can (semantically) be used whenever a
+        new result is set in addition to when an existing one is cleared. This is needed so that
+        `AuditTestCaseContentView` will refresh each time the result changes, instead of only in the
+        first `layout()` after the last result was cleared.
+
+        * UserInterface/Models/AuditTestCaseResult.js:
+        (WI.AuditTestCaseResult.async fromPayload):
+        (WI.AuditTestCaseResult.prototype.toJSON):
+        Drive-by: fix the check for optional `data` values to still warn if the value is `null`.
+
+        * UserInterface/Controllers/AuditManager.js:
+        (WI.AuditManager.prototype._addDefaultTests):
+        Add a default test "Demo Audit > Result Data > data-custom" as an example how to write an
+        Audit that returns custom data, as well as how that custom data is shown in the Audit tab.
+
+        * UserInterface/Views/AuditTestContentView.js:
+        (WI.AuditTestContentView.prototype.shown):
+        (WI.AuditTestContentView.prototype.handleResultChanged): Added.
+        * UserInterface/Views/AuditTestCaseContentView.js:
+        (WI.AuditTestCaseContentView):
+        (WI.AuditTestCaseContentView.prototype.layout):
+        (WI.AuditTestCaseContentView.prototype.handleResultChanged): Added.
+        Preserve the UI for each section across `layout()`s, so that expand/collapse states aren't
+        reset each time the user changes the selected Audit.
+
+        * UserInterface/Views/AuditTestCaseContentView.css:
+        (.content-view.audit-test-case > section table > tr > td + td): Added.
+        Drive-by: ensure that the "index" table column is never larger than it needs to be.
+
+        * UserInterface/Views/AuditTreeElement.js:
+        (WI.AuditTreeElement.prototype.onattach):
+        (WI.AuditTreeElement.prototype._handleTestResultChanged): Added.
+        (WI.AuditTreeElement.prototype._handleTestResultCleared): Deleted.
+
+        * Localizations/en.lproj/localizedStrings.js:
+
 2019-05-28  Devin Rousso  <[email protected]>
 
         Web Inspector: Timelines: spacing around pie chart is different between CPU and Memory

Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (245913 => 245914)


--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js	2019-05-31 00:12:03 UTC (rev 245914)
@@ -1081,8 +1081,9 @@
 localizedStrings["This action causes no visual change"] = "This action causes no visual change";
 localizedStrings["This action moves the path outside the visible area"] = "This action moves the path outside the visible area";
 localizedStrings["This audit is not supported"] = "This audit is not supported";
+localizedStrings["This is an example of how custom result data is shown."] = "This is an example of how custom result data is shown.";
 localizedStrings["This is an example of how errors are shown. The error was thrown manually, but execution errors will appear in the same way."] = "This is an example of how errors are shown. The error was thrown manually, but execution errors will appear in the same way.";
-localizedStrings["This is an example of how result DOM nodes are shown. It will pass with all elements with an id attribute."] = "This is an example of how result DOM nodes are shown. It will pass with all elements with an id attribute.";
+localizedStrings["This is an example of how result DOM attributes are highlighted on any returned DOM nodes. It will pass with all elements with an id attribute."] = "This is an example of how result DOM attributes are highlighted on any returned DOM nodes. It will pass with all elements with an id attribute.";
 localizedStrings["This is an example of how result DOM nodes are shown. It will pass with the <body> element."] = "This is an example of how result DOM nodes are shown. It will pass with the <body> element.";
 localizedStrings["This is what the result of a failing test with no data looks like."] = "This is what the result of a failing test with no data looks like.";
 localizedStrings["This is what the result of a passing test with no data looks like."] = "This is what the result of a passing test with no data looks like.";
@@ -1204,6 +1205,7 @@
 localizedStrings["\u0022%s\u0022 has a non-object \u0022%s\u0022 value"] = "\u0022%s\u0022 has a non-object \u0022%s\u0022 value";
 localizedStrings["\u0022%s\u0022 has a non-string \u0022%s\u0022 value"] = "\u0022%s\u0022 has a non-string \u0022%s\u0022 value";
 localizedStrings["\u0022%s\u0022 has an invalid \u0022%s\u0022 value"] = "\u0022%s\u0022 has an invalid \u0022%s\u0022 value";
+localizedStrings["\u0022%s\u0022 is not JSON serializable"] = "\u0022%s\u0022 is not JSON serializable";
 localizedStrings["\u0022%s\u0022 is not valid for %s"] = "\u0022%s\u0022 is not valid for %s";
 localizedStrings["\u0022%s\u0022 is too new to run in this Web Inspector"] = "\u0022%s\u0022 is too new to run in this Web Inspector";
 localizedStrings["\u0022%s\u0022 is too new to run on this inspected page"] = "\u0022%s\u0022 is too new to run on this inspected page";

Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js (245913 => 245914)


--- trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js	2019-05-31 00:12:03 UTC (rev 245914)
@@ -80,6 +80,19 @@
     }
 });
 
+Object.defineProperty(Object, "filter",
+{
+    value(object, callback)
+    {
+        let filtered = {};
+        for (let key in object) {
+            if (callback(key, object[key]))
+                filtered[key] = object[key];
+        }
+        return filtered;
+    }
+});
+
 Object.defineProperty(Object.prototype, "valueForCaseInsensitiveKey",
 {
     value(key)

Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js (245913 => 245914)


--- trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js	2019-05-31 00:12:03 UTC (rev 245914)
@@ -891,8 +891,9 @@
                 ], {description: WI.UIString("These are all of the different test result levels.")}),
                 new WI.AuditTestGroup(WI.UIString("Result Data"), [
                     new WI.AuditTestCase(`data-domNodes`, `function() { return {domNodes: [document.body], level: "pass"}; }`, {description: WI.UIString("This is an example of how result DOM nodes are shown. It will pass with the <body> element.")}),
-                    new WI.AuditTestCase(`data-domAttributes`, `function() { return {domNodes: Array.from(document.querySelectorAll("[id]")), domAttributes: ["id"], level: "pass"}; }`, {description: WI.UIString("This is an example of how result DOM nodes are shown. It will pass with all elements with an id attribute.")}),
+                    new WI.AuditTestCase(`data-domAttributes`, `function() { return {domNodes: Array.from(document.querySelectorAll("[id]")), domAttributes: ["id"], level: "pass"}; }`, {description: WI.UIString("This is an example of how result DOM attributes are highlighted on any returned DOM nodes. It will pass with all elements with an id attribute.")}),
                     new WI.AuditTestCase(`data-errors`, `function() { throw Error("this error was thrown from inside the audit test code."); }`, {description: WI.UIString("This is an example of how errors are shown. The error was thrown manually, but execution errors will appear in the same way.")}),
+                    new WI.AuditTestCase(`data-custom`, `function() { return {level: "pass", a: 1, b: [2], c: {key: 3}}; }`, {description: WI.UIString("This is an example of how custom result data is shown.")}),
                 ], {description: WI.UIString("These are all of the different types of data that can be returned with the test result.")}),
             ], {description: WI.UIString("These tests serve as a demonstration of the functionality and structure of audits.")}),
             new WI.AuditTestGroup(WI.UIString("Accessibility"), [

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js (245913 => 245914)


--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js	2019-05-31 00:12:03 UTC (rev 245914)
@@ -116,7 +116,7 @@
         } else {
             agentCommandFunction = RuntimeAgent.evaluate;
             agentCommandArguments._expression_ = `(function() { "use strict"; return eval(\`(${this._setup.replace(/`/g, "\\`")})\`)(); })()`;
-            agentCommandArguments.objectGroup = "audit";
+            agentCommandArguments.objectGroup = AuditTestBase.ObjectGroup;
             agentCommandArguments.doNotPauseOnExceptionsAndMuteConsole = true;
         }
 
@@ -191,8 +191,8 @@
 
         this._result = null;
 
-        if (!options.suppressResultClearedEvent)
-            this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultCleared);
+        if (!options.suppressResultChangedEvent)
+            this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
 
         return true;
     }
@@ -228,13 +228,15 @@
 };
 
 // Keep this in sync with Inspector::Protocol::Audit::VERSION.
-WI.AuditTestBase.Version = 2;
+WI.AuditTestBase.Version = 3;
 
+WI.AuditTestBase.ObjectGroup = "audit";
+
 WI.AuditTestBase.Event = {
     Completed: "audit-test-base-completed",
     DisabledChanged: "audit-test-base-disabled-changed",
     Progress: "audit-test-base-progress",
-    ResultCleared: "audit-test-base-result-cleared",
+    ResultChanged: "audit-test-base-result-changed",
     Scheduled: "audit-test-base-scheduled",
     Stopping: "audit-test-base-stopping",
 };

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js (245913 => 245914)


--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js	2019-05-31 00:12:03 UTC (rev 245914)
@@ -126,125 +126,173 @@
 
         async function parseResponse(response) {
             let remoteObject = WI.RemoteObject.fromPayload(response.result, WI.mainTarget);
-            if (response.wasThrown || (remoteObject.type === "object" && remoteObject.subtype === "error"))
+            if (response.wasThrown || (remoteObject.type === "object" && remoteObject.subtype === "error")) {
                 addError(remoteObject.description);
-            else if (remoteObject.type === "boolean")
+                return;
+            }
+
+            if (remoteObject.type === "boolean") {
                 setLevel(remoteObject.value ? WI.AuditTestCaseResult.Level.Pass : WI.AuditTestCaseResult.Level.Fail);
-            else if (remoteObject.type === "string")
+                return;
+            }
+
+            if (remoteObject.type === "string") {
                 setLevel(remoteObject.value.trim().toLowerCase());
-            else if (remoteObject.type === "object" && !remoteObject.subtype) {
-                const options = {
-                    ownProperties: true,
-                };
+                return;
+            }
 
-                let properties = await new Promise((resolve, reject) => remoteObject.getPropertyDescriptorsAsObject(resolve, options));
+            if (remoteObject.type !== "object" || remoteObject.subtype) {
+                addError(WI.UIString("Return value is not an object, string, or boolean"));
+                return;
+            }
 
-                function checkResultProperty(key, type, subtype) {
-                    if (!(key in properties))
-                        return null;
+            const options = {
+                ownProperties: true,
+            };
 
-                    let property = properties[key].value;
-                    if (!property)
-                        return null;
+            function checkResultProperty(key, value, type, subtype) {
+                function addErrorForValueType(valueType) {
+                    let errorString = null;
+                    if (valueType === "object" || valueType === "array")
+                        errorString = WI.UIString("\u0022%s\u0022 must be an %s");
+                    else
+                        errorString = WI.UIString("\u0022%s\u0022 must be a %s");
+                    addError(errorString.format(key, valueType));
+                }
 
-                    function addErrorForValueType(valueType) {
-                        let value = null;
-                        if (valueType === "object" || valueType === "array")
-                            value = WI.UIString("\u0022%s\u0022 must be an %s");
-                        else
-                            value = WI.UIString("\u0022%s\u0022 must be a %s");
-                        addError(value.format(key, valueType));
-                    }
+                if (value.subtype !== subtype) {
+                    addErrorForValueType(subtype);
+                    return null;
+                }
 
-                    if (property.subtype !== subtype) {
-                        addErrorForValueType(subtype);
-                        return null;
-                    }
+                if (value.type !== type) {
+                    addErrorForValueType(type);
+                    return null;
+                }
 
-                    if (property.type !== type) {
-                        addErrorForValueType(type);
-                        return null;
-                    }
+                if (type === "boolean" || type === "string")
+                    return value.value;
 
-                    if (type === "boolean" || type === "string")
-                        return property.value;
+                return value;
+            }
 
-                    return property;
+            async function resultArrayForEach(key, value, callback) {
+                let array = checkResultProperty(key, value, "object", "array");
+                if (!array)
+                    return;
+
+                // `getPropertyDescriptorsAsObject` returns an object, meaning that if we
+                // want to iterate over `array` by index, we have to count.
+                let asObject = await new Promise((resolve, reject) => array.getPropertyDescriptorsAsObject(resolve, options));
+                for (let i = 0; i < array.size; ++i) {
+                    if (i in asObject)
+                        await callback(asObject[i]);
                 }
+            }
 
-                async function resultArrayForEach(key, callback) {
-                    let array = checkResultProperty(key, "object", "array");
-                    if (!array)
-                        return;
+            let properties = await new Promise((resolve, reject) => remoteObject.getPropertyDescriptors(resolve, options));
+            for (let property of properties) {
+                let key = property.name;
+                if (key === "__proto__")
+                    continue;
 
-                    // `getPropertyDescriptorsAsObject` returns an object, meaning that if we
-                    // want to iterate over `array` by index, we have to count.
-                    let asObject = await new Promise((resolve, reject) => array.getPropertyDescriptorsAsObject(resolve, options));
-                    for (let i = 0; i < array.size; ++i) {
-                        if (i in asObject)
-                            await callback(asObject[i]);
-                    }
+                let value = property.value;
+
+                switch (key) {
+                case "level": {
+                    let levelString = checkResultProperty(key, value, "string");
+                    if (levelString)
+                        setLevel(levelString.trim().toLowerCase());
+                    break;
                 }
 
-                let levelString = checkResultProperty("level", "string");
-                if (levelString)
-                    setLevel(levelString.trim().toLowerCase());
+                case "pass":
+                    if (checkResultProperty(key, value, "boolean"))
+                        setLevel(WI.AuditTestCaseResult.Level.Pass);
+                    break;
 
-                if (checkResultProperty("pass", "boolean"))
-                    setLevel(WI.AuditTestCaseResult.Level.Pass);
-                if (checkResultProperty("warn", "boolean"))
-                    setLevel(WI.AuditTestCaseResult.Level.Warn);
-                if (checkResultProperty("fail", "boolean"))
-                    setLevel(WI.AuditTestCaseResult.Level.Fail);
-                if (checkResultProperty("error", "boolean"))
-                    setLevel(WI.AuditTestCaseResult.Level.Error);
-                if (checkResultProperty("unsupported", "boolean"))
-                    setLevel(WI.AuditTestCaseResult.Level.Unsupported);
+                case "warn":
+                    if (checkResultProperty(key, value, "boolean"))
+                        setLevel(WI.AuditTestCaseResult.Level.Warn);
+                    break;
 
-                await resultArrayForEach("domNodes", async (item) => {
-                    if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "node") {
-                        addError(WI.UIString("All items in \u0022%s\u0022 must be valid DOM nodes").format(WI.unlocalizedString("domNodes")));
-                        return;
-                    }
+                case "fail":
+                    if (checkResultProperty(key, value, "boolean"))
+                        setLevel(WI.AuditTestCaseResult.Level.Fail);
+                    break;
 
-                    let domNodeId = await new Promise((resolve, reject) => item.value.pushNodeToFrontend(resolve));
-                    let domNode = WI.domManager.nodeForId(domNodeId);
-                    if (!domNode)
-                        return;
+                case "error":
+                    if (checkResultProperty(key, value, "boolean"))
+                        setLevel(WI.AuditTestCaseResult.Level.Error);
+                    break;
 
-                    if (!data.domNodes)
-                        data.domNodes = [];
-                    data.domNodes.push(WI.cssPath(domNode, {full: true}));
+                case "unsupported":
+                    if (checkResultProperty(key, value, "boolean"))
+                        setLevel(WI.AuditTestCaseResult.Level.Unsupported);
+                    break;
 
-                    if (!resolvedDOMNodes)
-                        resolvedDOMNodes = [];
-                    resolvedDOMNodes.push(domNode);
-                });
+                case "domNodes":
+                    await resultArrayForEach(key, value, async (item) => {
+                        if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "node") {
+                            addError(WI.UIString("All items in \u0022%s\u0022 must be valid DOM nodes").format(WI.unlocalizedString("domNodes")));
+                            return;
+                        }
 
-                await resultArrayForEach("domAttributes", (item) => {
-                    if (!item || !item.value || item.value.type !== "string" || !item.value.value.length) {
-                        addError(WI.UIString("All items in \u0022%s\u0022 must be non-empty strings").format(WI.unlocalizedString("domAttributes")));
-                        return;
-                    }
+                        let domNodeId = await new Promise((resolve, reject) => item.value.pushNodeToFrontend(resolve));
+                        let domNode = WI.domManager.nodeForId(domNodeId);
+                        if (!domNode)
+                            return;
 
-                    if (!data.domAttributes)
-                        data.domAttributes = [];
-                    data.domAttributes.push(item.value.value);
-                });
+                        if (!data.domNodes)
+                            data.domNodes = [];
+                        data.domNodes.push(WI.cssPath(domNode, {full: true}));
 
-                await resultArrayForEach("errors", (item) => {
-                    if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "error") {
-                        addError(WI.UIString("All items in \u0022%s\u0022 must be error objects").format(WI.unlocalizedString("errors")));
-                        return;
-                    }
+                        if (!resolvedDOMNodes)
+                            resolvedDOMNodes = [];
+                        resolvedDOMNodes.push(domNode);
+                    });
+                    break;
 
-                    addError(item.value.description);
-                });
+                case "domAttributes":
+                    await resultArrayForEach(key, value, (item) => {
+                        if (!item || !item.value || item.value.type !== "string" || !item.value.value.length) {
+                            addError(WI.UIString("All items in \u0022%s\u0022 must be non-empty strings").format(WI.unlocalizedString("domAttributes")));
+                            return;
+                        }
 
-                if (window.InspectorTest && properties.__test)
-                    data.__test = properties.__test.value;
-            } else
-                addError(WI.UIString("Return value is not an object, string, or boolean"));
+                        if (!data.domAttributes)
+                            data.domAttributes = [];
+                        data.domAttributes.push(item.value.value);
+                    });
+                    break;
+
+                case "errors":
+                    await resultArrayForEach(key, value, (item) => {
+                        if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "error") {
+                            addError(WI.UIString("All items in \u0022%s\u0022 must be error objects").format(WI.unlocalizedString("errors")));
+                            return;
+                        }
+
+                        addError(item.value.description);
+                    });
+                    break;
+
+                default:
+                    if (value.objectId) {
+                        try {
+                            function inspectedPage_stringify() {
+                                return JSON.stringify(this);
+                            }
+                            let stringifiedValue = await value.callFunction(inspectedPage_stringify);
+                            data[key] = JSON.parse(stringifiedValue.value);
+                        } catch {
+                            addError(WI.UIString("\u0022%s\u0022 is not JSON serializable").format(key));
+                        }
+                    } else
+                        data[key] = value.value;
+                    break;
+                }
+            }
         }
 
         let agentCommandFunction = null;
@@ -255,7 +303,7 @@
         } else {
             agentCommandFunction = RuntimeAgent.evaluate;
             agentCommandArguments._expression_ = `(function() { "use strict"; return eval(\`(${this._test.replace(/`/g, "\\`")})\`)(); })()`;
-            agentCommandArguments.objectGroup = "audit";
+            agentCommandArguments.objectGroup = WI.AuditTestCase.ObjectGroup;
             agentCommandArguments.doNotPauseOnExceptionsAndMuteConsole = true;
         }
 
@@ -295,6 +343,8 @@
         if (resolvedDOMNodes)
             options.resolvedDOMNodes = resolvedDOMNodes;
         this._result = new WI.AuditTestCaseResult(this.name, level, options);
+
+        this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
     }
 };
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCaseResult.js (245913 => 245914)


--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCaseResult.js	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCaseResult.js	2019-05-31 00:12:03 UTC (rev 245914)
@@ -67,7 +67,7 @@
             payload.data = ""
         } else {
             function checkArray(key) {
-                if (!payload.data[key])
+                if (!(key in payload.data))
                     return;
 
                 if (!Array.isArray(payload.data[key])) {
@@ -132,24 +132,27 @@
 
         if (!isEmptyObject(payload.data)) {
             options.data = ""
-            if (payload.data.domNodes && payload.data.domNodes.length) {
-                if (window.DOMAgent && (!payload.metadata.url || payload.metadata.url ="" WI.networkManager.mainFrame.url)) {
-                    let documentNode = await new Promise((resolve) => WI.domManager.requestDocument(resolve));
-                    options.resolvedDOMNodes = await Promise.all(payload.data.domNodes.map(async (domNodeString) => {
-                        let nodeId = 0;
-                        try {
-                            nodeId = await WI.domManager.querySelector(documentNode, domNodeString);
-                        } catch { }
-                        return WI.domManager.nodeForId(nodeId) || null;
-                    }));
+            for (let key in payload.data) {
+                if (key === "domNodes" || key === "domAttributes" || key === "errors") {
+                    if (!payload.data[key].length)
+                        continue;
                 }
 
-                options.data.domNodes = payload.data.domNodes;
+                if (key === "domNodes") {
+                    if (window.DOMAgent && (!payload.metadata.url || payload.metadata.url ="" WI.networkManager.mainFrame.url)) {
+                        let documentNode = await new Promise((resolve) => WI.domManager.requestDocument(resolve));
+                        options.resolvedDOMNodes = await Promise.all(payload.data.domNodes.map(async (domNodeString) => {
+                            let nodeId = 0;
+                            try {
+                                nodeId = await WI.domManager.querySelector(documentNode, domNodeString);
+                            } catch { }
+                            return WI.domManager.nodeForId(nodeId) || null;
+                        }));
+                    }
+                }
+
+                options.data[key] = payload.data[key];
             }
-            if (payload.data.domAttributes && payload.data.domAttributes.length)
-                options.data.domAttributes = payload.data.domAttributes;
-            if (payload.data.errors && payload.data.errors.length)
-                options.data.errors = payload.data.errors;
         }
 
         if (!isEmptyObject(payload.metadata)) {
@@ -210,13 +213,14 @@
         json.level = this._level;
 
         let data = ""
-        if (this._data.domNodes && this._data.domNodes.length) {
-            data.domNodes = this._data.domNodes;
-            if (this._data.domAttributes && this._data.domAttributes.length)
-                data.domAttributes = this._data.domAttributes;
+        for (let key in this._data) {
+            if (key === "domNodes" || key === "domAttributes" || key === "errors") {
+                if (!this._data[key].length)
+                    continue;
+            }
+
+            data[key] = this._data[key];
         }
-        if (this._data.errors && this._data.errors.length)
-            data.errors = this._data.errors;
         if (!isEmptyObject(data))
             json.data = ""
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js (245913 => 245914)


--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js	2019-05-31 00:12:03 UTC (rev 245914)
@@ -169,7 +169,7 @@
 
         return super.clearResult({
             ...options,
-            suppressResultClearedEvent: !cleared,
+            suppressResultChangedEvent: !cleared,
         });
     }
 
@@ -210,6 +210,8 @@
         this._result = new WI.AuditTestGroupResult(this.name, results, {
             description: this.description,
         });
+
+        this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
     }
 
     _handleTestCompleted(event)

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css (245913 => 245914)


--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css	2019-05-31 00:12:03 UTC (rev 245914)
@@ -127,6 +127,10 @@
     top: -1px;
 }
 
+.content-view.audit-test-case > section table > tr > td + td {
+    width: 100%;
+}
+
 .content-view.audit-test-case > section .CodeMirror {
     width: 100%;
     height: auto;

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js (245913 => 245914)


--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js	2019-05-31 00:12:03 UTC (rev 245914)
@@ -32,6 +32,10 @@
         super(representedObject);
 
         this.element.classList.add("audit-test-case");
+
+        this._resultDataGeneralContainer = null;
+        this._resultDataDOMNodesContainer = null;
+        this._resultDataErrorsContainer = null;
     }
 
     // Protected
@@ -133,14 +137,52 @@
 
         let resultData = result.data;
 
-        if (resultData.domNodes && resultData.domNodes.length) {
-            let domNodesContainer = this.contentView.element.appendChild(document.createElement("div"));
-            domNodesContainer.classList.add("dom-nodes");
+        if (!this._resultDataGeneralContainer) {
+            let nonSpecialData = Object.filter(resultData, (key) => key !== "domNodes" && key !== "errors");
+            if (!isEmptyObject(nonSpecialData)) {
+                this._resultDataGeneralContainer = document.createElement("div");
 
-            let domNodeText = domNodesContainer.appendChild(document.createElement("h1"));
+                let _expression_ = "(" + JSON.stringify(nonSpecialData) + ")";
+                const options = {
+                    objectGroup: WI.AuditTestBase.ObjectGroup,
+                    doNotPauseOnExceptionsAndMuteConsole: true,
+                };
+                WI.runtimeManager.evaluateInInspectedWindow(_expression_, options, (nonSpecialDataRemoteObject, wasThrown) => {
+                    console.assert(!wasThrown);
+                    if (!nonSpecialDataRemoteObject)
+                        return;
+
+                    if (!this.representedObject.result || this.representedObject.result.data !== resultData)
+                        return;
+
+                    const propertyPath = null;
+                    const forceExpanding = true;
+                    let element = WI.FormattedValue.createObjectTreeOrFormattedValueForRemoteObject(nonSpecialDataRemoteObject, propertyPath, forceExpanding);
+
+                    let objectTree = element.__objectTree;
+                    if (objectTree) {
+                        objectTree.showOnlyProperties();
+                        objectTree.expand();
+                    }
+
+                    this._resultDataGeneralContainer.appendChild(element);
+
+                    this.hidePlaceholder();
+                });
+            }
+        }
+
+        if (this._resultDataGeneralContainer)
+            this.contentView.element.appendChild(this._resultDataGeneralContainer);
+
+        if (!this._resultDataDOMNodesContainer && resultData.domNodes && resultData.domNodes.length) {
+            this._resultDataDOMNodesContainer = document.createElement("div");
+            this._resultDataDOMNodesContainer.classList.add("dom-nodes");
+
+            let domNodeText = this._resultDataDOMNodesContainer.appendChild(document.createElement("h1"));
             domNodeText.textContent = WI.UIString("DOM Nodes:");
 
-            let tableContainer = domNodesContainer.appendChild(document.createElement("table"));
+            let tableContainer = this._resultDataDOMNodesContainer.appendChild(document.createElement("table"));
 
             resultData.domNodes.forEach((domNode, index) => {
                 domNode = result.resolvedDOMNodes[index] || domNode;
@@ -204,14 +246,17 @@
             });
         }
 
-        if (resultData.errors && resultData.errors.length) {
-            let errorContainer = this.contentView.element.appendChild(document.createElement("div"));
-            errorContainer.classList.add("errors");
+        if (this._resultDataDOMNodesContainer)
+            this.contentView.element.appendChild(this._resultDataDOMNodesContainer);
 
-            let errorText = errorContainer.appendChild(document.createElement("h1"));
+        if (!this._resultDataErrorsContainer && resultData.errors && resultData.errors.length) {
+            this._resultDataErrorsContainer = document.createElement("div");
+            this._resultDataErrorsContainer.classList.add("errors");
+
+            let errorText = this._resultDataErrorsContainer.appendChild(document.createElement("h1"));
             errorText.textContent = WI.UIString("Errors:");
 
-            let tableContainer = errorContainer.appendChild(document.createElement("table"));
+            let tableContainer = this._resultDataErrorsContainer.appendChild(document.createElement("table"));
 
             resultData.errors.forEach((error, index) => {
                 let rowElement = tableContainer.appendChild(document.createElement("tr"));
@@ -227,10 +272,22 @@
             });
         }
 
+        if (this._resultDataErrorsContainer)
+            this.contentView.element.appendChild(this._resultDataErrorsContainer);
+
         if (!this.contentView.element.children.length)
             this.showNoResultDataPlaceholder();
     }
 
+    handleResultChanged(event)
+    {
+        super.handleResultChanged(event);
+
+        this._resultDataGeneralContainer = null;
+        this._resultDataDOMNodesContainer = null;
+        this._resultDataErrorsContainer = null;
+    }
+
     showRunningPlaceholder()
     {
         if (!this.placeholderElement || !this.placeholderElement.__placeholderRunning) {

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js (245913 => 245914)


--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js	2019-05-31 00:12:03 UTC (rev 245914)
@@ -102,7 +102,7 @@
         if (this.representedObject instanceof WI.AuditTestBase) {
             this.representedObject.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestChanged, this);
             this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestChanged, this);
-            this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultCleared, this._handleTestChanged, this);
+            this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultChanged, this.handleResultChanged, this);
             this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestChanged, this);
             this.representedObject.addEventListener(WI.AuditTestBase.Event.Stopping, this._handleTestChanged, this);
         }
@@ -116,6 +116,13 @@
         super.hidden();
     }
 
+    handleResultChanged(event)
+    {
+        // Overridden by sub-classes.
+
+        this.needsLayout();
+    }
+
     get placeholderElement()
     {
         return this._placeholderElement;

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js (245913 => 245914)


--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js	2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js	2019-05-31 00:12:03 UTC (rev 245914)
@@ -62,7 +62,7 @@
 
         if (this.representedObject instanceof WI.AuditTestBase) {
             this.representedObject.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
-            this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultCleared, this._handleTestResultCleared, this);
+            this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultChanged, this._handleTestResultChanged, this);
 
             if (this.representedObject instanceof WI.AuditTestCase)
                 this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestCaseScheduled, this);
@@ -281,7 +281,7 @@
             this._updateTestGroupDisabled();
     }
 
-    _handleTestResultCleared(event)
+    _handleTestResultChanged(event)
     {
         this._updateStatus();
     }
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to