<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[245914] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/245914">245914</a></dd>
<dt>Author</dt> <dd>drousso@apple.com</dd>
<dt>Date</dt> <dd>2019-05-30 17:12:03 -0700 (Thu, 30 May 2019)</dd>
</dl>

<h3>Log Message</h3>
<pre>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/JavaScriptCore:

* 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:</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkLayoutTestsinspectorauditmanagerstartsetupexpectedtxt">trunk/LayoutTests/inspector/audit/manager-start-setup-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorauditmanagerstartsetuphtml">trunk/LayoutTests/inspector/audit/manager-start-setup.html</a></li>
<li><a href="#trunkLayoutTestsinspectormodelauditTestCaseexpectedtxt">trunk/LayoutTests/inspector/model/auditTestCase-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectormodelauditTestCaseResultexpectedtxt">trunk/LayoutTests/inspector/model/auditTestCaseResult-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectormodelauditTestGroupexpectedtxt">trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorunittestsobjectutilitiesexpectedtxt">trunk/LayoutTests/inspector/unit-tests/object-utilities-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorunittestsobjectutilitieshtml">trunk/LayoutTests/inspector/unit-tests/object-utilities.html</a></li>
<li><a href="#trunkSourceJavaScriptCoreChangeLog">trunk/Source/JavaScriptCore/ChangeLog</a></li>
<li><a href="#trunkSourceJavaScriptCoreinspectorprotocolAuditjson">trunk/Source/JavaScriptCore/inspector/protocol/Audit.json</a></li>
<li><a href="#trunkSourceWebInspectorUIChangeLog">trunk/Source/WebInspectorUI/ChangeLog</a></li>
<li><a href="#trunkSourceWebInspectorUILocalizationsenlprojlocalizedStringsjs">trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceBaseUtilitiesjs">trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceControllersAuditManagerjs">trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceModelsAuditTestBasejs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceModelsAuditTestCasejs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceModelsAuditTestCaseResultjs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCaseResult.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceModelsAuditTestGroupjs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsAuditTestCaseContentViewcss">trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsAuditTestCaseContentViewjs">trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsAuditTestContentViewjs">trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsAuditTreeElementjs">trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog      2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/LayoutTests/ChangeLog 2019-05-31 00:12:03 UTC (rev 245914)
</span><span class="lines">@@ -1,3 +1,19 @@
</span><ins>+2019-05-30  Devin Rousso  <drousso@apple.com>
+
+        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:
+
</ins><span class="cx"> 2019-05-30  Andres Gonzalez  <andresg_22@apple.com>
</span><span class="cx"> 
</span><span class="cx">         Inserting a newline in contenteditable causes two characters to be added instead of one
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditmanagerstartsetupexpectedtxt"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/manager-start-setup-expected.txt (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -3,18 +3,18 @@
</span><span class="cx"> 
</span><span class="cx"> == Running test suite: AuditManager.prototype.start
</span><span class="cx"> -- Running test case: AuditManager.prototype.start.SyncSetup
</span><del>-PASS: The setup function should have set __test to 42.
</del><ins>+PASS: The setup function should have set test to 42.
</ins><span class="cx"> 
</span><span class="cx"> -- Running test case: AuditManager.prototype.start.AsyncSetup
</span><del>-PASS: The setup function should have set __test to 42.
</del><ins>+PASS: The setup function should have set test to 42.
</ins><span class="cx"> 
</span><span class="cx"> -- Running test case: AuditManager.prototype.start.SubLevelSetup
</span><del>-PASS: The setup function should have set __test to undefined.
</del><ins>+PASS: The setup function should have set test to undefined.
</ins><span class="cx"> 
</span><span class="cx"> -- Running test case: AuditManager.prototype.start.OverriddenSetup
</span><del>-PASS: The setup function should have set __test to B.
</del><ins>+PASS: The setup function should have set test to B.
</ins><span class="cx"> 
</span><span class="cx"> -- Running test case: AuditManager.prototype.start.MultipleTopLevel
</span><del>-PASS: The setup function should have set __test to A.
-PASS: The setup function should have set __test to B.
</del><ins>+PASS: The setup function should have set test to A.
+PASS: The setup function should have set test to B.
</ins><span class="cx"> 
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditmanagerstartsetuphtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/manager-start-setup.html (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -7,11 +7,11 @@
</span><span class="cx"> function test()
</span><span class="cx"> {
</span><span class="cx">     const auditTestString = (function() {
</span><del>-        return {
-            level: "pass",
-            __test: WebInspectorAudit.__test,
-        };
-    }).toString();
</del><ins>+    return {
+        level: "pass",
+        test: WebInspectorAudit.test,
+    };
+}).toString();
</ins><span class="cx"> 
</span><span class="cx">     async function wrapTest(audit, expected, {getResultCallback} = {}) {
</span><span class="cx">         WI.auditManager._addTest(audit);
</span><span class="lines">@@ -22,8 +22,11 @@
</span><span class="cx"> 
</span><span class="cx">         InspectorTest.assert(result.didPass, "The test should pass.");
</span><span class="cx">         InspectorTest.assert(!result.data.errors, "There should be no errors.");
</span><del>-        InspectorTest.expectEqual(result.data.__test.value, expected, `The setup function should have set __test to ${expected}.`);
</del><ins>+        if (result.data.errors)
+            InspectorTest.json(result.data.errors);
</ins><span class="cx"> 
</span><ins>+        InspectorTest.expectEqual(result.data.test, expected, `The setup function should have set test to ${expected}.`);
+
</ins><span class="cx">         WI.auditManager.removeTest(audit);
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="lines">@@ -34,7 +37,7 @@
</span><span class="cx">         description: "Check that the setup of an audit is actually run.",
</span><span class="cx">         async test() {
</span><span class="cx">             const setup = (function() {
</span><del>-                WebInspectorAudit.__test = 42;
</del><ins>+                WebInspectorAudit.test = 42;
</ins><span class="cx">             }).toString();
</span><span class="cx"> 
</span><span class="cx">             let audit = new WI.AuditTestCase("AuditManager.prototype.start.SyncSetup", auditTestString, {setup});
</span><span class="lines">@@ -52,7 +55,7 @@
</span><span class="cx">                     setTimeout(resolve, 10);
</span><span class="cx">                 });
</span><span class="cx"> 
</span><del>-                WebInspectorAudit.__test = 42;
</del><ins>+                WebInspectorAudit.test = 42;
</ins><span class="cx">             }).toString();
</span><span class="cx"> 
</span><span class="cx">             let audit = new WI.AuditTestCase("AuditManager.prototype.start.AsyncSetup", auditTestString, {setup});
</span><span class="lines">@@ -66,7 +69,7 @@
</span><span class="cx">         description: "Check that the setup of a non-top-level audit is not run.",
</span><span class="cx">         async test() {
</span><span class="cx">             const setup = (function() {
</span><del>-                WebInspectorAudit.__test = 42;
</del><ins>+                WebInspectorAudit.test = 42;
</ins><span class="cx">             }).toString();
</span><span class="cx"> 
</span><span class="cx">             let audit = new WI.AuditTestGroup("AuditManager.prototype.start.SubLevelSetup.Group", [
</span><span class="lines">@@ -86,15 +89,15 @@
</span><span class="cx">         description: "Check that only the setup of top-level audits is run.",
</span><span class="cx">         async test() {
</span><span class="cx">             const setupA = (function() {
</span><del>-                if (!WebInspectorAudit.__test)
-                    WebInspectorAudit.__test = "";
-                WebInspectorAudit.__test += "A";
</del><ins>+                if (!WebInspectorAudit.test)
+                    WebInspectorAudit.test = "";
+                WebInspectorAudit.test += "A";
</ins><span class="cx">             }).toString();
</span><span class="cx"> 
</span><span class="cx">             const setupB = (function() {
</span><del>-                if (!WebInspectorAudit.__test)
-                    WebInspectorAudit.__test = "";
-                WebInspectorAudit.__test += "B";
</del><ins>+                if (!WebInspectorAudit.test)
+                    WebInspectorAudit.test = "";
+                WebInspectorAudit.test += "B";
</ins><span class="cx">             }).toString();
</span><span class="cx"> 
</span><span class="cx">             let audit = new WI.AuditTestGroup("AuditManager.prototype.start.OverriddenLevelSetup.Group", [
</span><span class="lines">@@ -114,9 +117,9 @@
</span><span class="cx">         description: "Test that a new WebInspectorAudit object is created for each setup call.",
</span><span class="cx">         async test() {
</span><span class="cx">             const setupA = (function() {
</span><del>-                if (!WebInspectorAudit.__test)
-                    WebInspectorAudit.__test = "";
-                WebInspectorAudit.__test += "A";
</del><ins>+                if (!WebInspectorAudit.test)
+                    WebInspectorAudit.test = "";
+                WebInspectorAudit.test += "A";
</ins><span class="cx">             }).toString();
</span><span class="cx"> 
</span><span class="cx">             let auditA = new WI.AuditTestCase("AuditManager.prototype.start.MultipleTopLevel.A", auditTestString, {setup: setupA});
</span><span class="lines">@@ -124,9 +127,9 @@
</span><span class="cx">             await wrapTest(auditA, "A");
</span><span class="cx"> 
</span><span class="cx">             const setupB = (function() {
</span><del>-                if (!WebInspectorAudit.__test)
-                    WebInspectorAudit.__test = "";
-                WebInspectorAudit.__test += "B";
</del><ins>+                if (!WebInspectorAudit.test)
+                    WebInspectorAudit.test = "";
+                WebInspectorAudit.test += "B";
</ins><span class="cx">             }).toString();
</span><span class="cx"> 
</span><span class="cx">             let auditB = new WI.AuditTestCase("AuditManager.prototype.start.MultipleTopLevel.B", auditTestString, {setup: setupB});
</span></span></pre></div>
<a id="trunkLayoutTestsinspectormodelauditTestCaseexpectedtxt"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/model/auditTestCase-expected.txt (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -28,7 +28,7 @@
</span><span class="cx"> {
</span><span class="cx">   "type": "test-case",
</span><span class="cx">   "name": "validWithInvalidOptionals test name",
</span><del>-  "supports": 3,
</del><ins>+  "supports": 4,
</ins><span class="cx">   "test": "validWithInvalidOptionals test function"
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -37,7 +37,7 @@
</span><span class="cx">   "type": "test-case",
</span><span class="cx">   "name": "validWithValidOptionals test name",
</span><span class="cx">   "description": "validWithValidOptionals test description",
</span><del>-  "supports": 1,
</del><ins>+  "supports": 2,
</ins><span class="cx">   "setup": "validWithValidOptionals test setup",
</span><span class="cx">   "test": "validWithValidOptionals test function"
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkLayoutTestsinspectormodelauditTestCaseResultexpectedtxt"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/model/auditTestCaseResult-expected.txt (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -32,6 +32,9 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> -- Running test case: AuditTestCaseResult.fromPayload.validWithInvalidSubOptionals
</span><ins>+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
</ins><span class="cx"> WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-object "metadata.startTimestamp" value
</span><span class="cx"> WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-object "metadata.asyncTimestamp" value
</span><span class="cx"> WARN: Audit Warning: "validWithInvalidSubOptionals test result name" has a non-object "metadata.endTimestamp" value
</span></span></pre></div>
<a id="trunkLayoutTestsinspectormodelauditTestGroupexpectedtxt"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -43,12 +43,12 @@
</span><span class="cx"> {
</span><span class="cx">   "type": "test-group",
</span><span class="cx">   "name": "validWithInvalidOptionals group name",
</span><del>-  "supports": 3,
</del><ins>+  "supports": 4,
</ins><span class="cx">   "tests": [
</span><span class="cx">     {
</span><span class="cx">       "type": "test-case",
</span><span class="cx">       "name": "validWithInvalidOptionals test name",
</span><del>-      "supports": 4,
</del><ins>+      "supports": 5,
</ins><span class="cx">       "test": "validWithInvalidOptionals test function"
</span><span class="cx">     }
</span><span class="cx">   ]
</span><span class="lines">@@ -59,7 +59,7 @@
</span><span class="cx">   "type": "test-group",
</span><span class="cx">   "name": "validWithValidOptionals group name",
</span><span class="cx">   "description": "validWithValidOptionals group description",
</span><del>-  "supports": 1,
</del><ins>+  "supports": 2,
</ins><span class="cx">   "setup": "validWithValidOptionals group setup",
</span><span class="cx">   "tests": [
</span><span class="cx">     {
</span><span class="lines">@@ -66,7 +66,7 @@
</span><span class="cx">       "type": "test-case",
</span><span class="cx">       "name": "validWithValidOptionals test name",
</span><span class="cx">       "description": "validWithValidOptionals test description",
</span><del>-      "supports": 0,
</del><ins>+      "supports": 1,
</ins><span class="cx">       "setup": "validWithValidOptionals test setup",
</span><span class="cx">       "test": "validWithValidOptionals test function"
</span><span class="cx">     }
</span><span class="lines">@@ -78,7 +78,7 @@
</span><span class="cx">   "type": "test-group",
</span><span class="cx">   "name": "validNested group name",
</span><span class="cx">   "description": "validNested group description",
</span><del>-  "supports": 1,
</del><ins>+  "supports": 2,
</ins><span class="cx">   "setup": "validNested group setup",
</span><span class="cx">   "tests": [
</span><span class="cx">     {
</span><span class="lines">@@ -85,7 +85,7 @@
</span><span class="cx">       "type": "test-group",
</span><span class="cx">       "name": "validNested nested group name",
</span><span class="cx">       "description": "validNested nested group description",
</span><del>-      "supports": 0,
</del><ins>+      "supports": 1,
</ins><span class="cx">       "setup": "validNested nested group setup",
</span><span class="cx">       "tests": [
</span><span class="cx">         {
</span><span class="lines">@@ -92,7 +92,7 @@
</span><span class="cx">           "type": "test-case",
</span><span class="cx">           "name": "validNested nested test name",
</span><span class="cx">           "description": "validNested nested test description",
</span><del>-          "supports": -1,
</del><ins>+          "supports": 0,
</ins><span class="cx">           "setup": "validNested nested test setup",
</span><span class="cx">           "test": "validNested nested test function"
</span><span class="cx">         }
</span><span class="lines">@@ -102,7 +102,7 @@
</span><span class="cx">       "type": "test-case",
</span><span class="cx">       "name": "validNested test name",
</span><span class="cx">       "description": "validNested test description",
</span><del>-      "supports": -2,
</del><ins>+      "supports": -1,
</ins><span class="cx">       "setup": "validNested test setup",
</span><span class="cx">       "test": "validNested test function"
</span><span class="cx">     }
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorunittestsobjectutilitiesexpectedtxt"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/unit-tests/object-utilities-expected.txt (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -22,3 +22,13 @@
</span><span class="cx"> PASS: shallowEqual of objects with different constructors should be false.
</span><span class="cx"> PASS: shallowEqual of objects with different constructors should be false.
</span><span class="cx"> 
</span><ins>+-- 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].
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectorunittestsobjectutilitieshtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/unit-tests/object-utilities.html (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -51,6 +51,29 @@
</span><span class="cx">         }
</span><span class="cx">     });
</span><span class="cx"> 
</span><ins>+    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], {});
+        }
+    });
+
</ins><span class="cx">     suite.runTestCasesAndFinish();
</span><span class="cx"> }
</span><span class="cx"> </script>
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/ChangeLog (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/ChangeLog    2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/JavaScriptCore/ChangeLog       2019-05-31 00:12:03 UTC (rev 245914)
</span><span class="lines">@@ -1,5 +1,16 @@
</span><span class="cx"> 2019-05-30  Devin Rousso  <drousso@apple.com>
</span><span class="cx"> 
</span><ins>+        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  <drousso@apple.com>
+
</ins><span class="cx">         Web Inspector: Audit: tests are unable to get the current Audit version
</span><span class="cx">         https://bugs.webkit.org/show_bug.cgi?id=198270
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreinspectorprotocolAuditjson"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/inspector/protocol/Audit.json (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/inspector/protocol/Audit.json        2019-05-31 00:08:04 UTC (rev 245913)
+++ trunk/Source/JavaScriptCore/inspector/protocol/Audit.json   2019-05-31 00:12:03 UTC (rev 245914)
</span><span class="lines">@@ -1,7 +1,7 @@
</span><span class="cx"> {
</span><span class="cx">     "domain": "Audit",
</span><span class="cx">     "description": "",
</span><del>-    "version": 2,
</del><ins>+    "version": 3,
</ins><span class="cx">     "commands": [
</span><span class="cx">         {
</span><span class="cx">             "name": "setup",
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/ChangeLog (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1,3 +1,72 @@
</span><ins>+2019-05-30  Devin Rousso  <drousso@apple.com>
+
+        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:
+
</ins><span class="cx"> 2019-05-28  Devin Rousso  <drousso@apple.com>
</span><span class="cx"> 
</span><span class="cx">         Web Inspector: Timelines: spacing around pie chart is different between CPU and Memory
</span></span></pre></div>
<a id="trunkSourceWebInspectorUILocalizationsenlprojlocalizedStringsjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1081,8 +1081,9 @@
</span><span class="cx"> localizedStrings["This action causes no visual change"] = "This action causes no visual change";
</span><span class="cx"> localizedStrings["This action moves the path outside the visible area"] = "This action moves the path outside the visible area";
</span><span class="cx"> localizedStrings["This audit is not supported"] = "This audit is not supported";
</span><ins>+localizedStrings["This is an example of how custom result data is shown."] = "This is an example of how custom result data is shown.";
</ins><span class="cx"> 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.";
</span><del>-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.";
</del><ins>+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.";
</ins><span class="cx"> 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.";
</span><span class="cx"> 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.";
</span><span class="cx"> 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.";
</span><span class="lines">@@ -1204,6 +1205,7 @@
</span><span class="cx"> localizedStrings["\u0022%s\u0022 has a non-object \u0022%s\u0022 value"] = "\u0022%s\u0022 has a non-object \u0022%s\u0022 value";
</span><span class="cx"> localizedStrings["\u0022%s\u0022 has a non-string \u0022%s\u0022 value"] = "\u0022%s\u0022 has a non-string \u0022%s\u0022 value";
</span><span class="cx"> localizedStrings["\u0022%s\u0022 has an invalid \u0022%s\u0022 value"] = "\u0022%s\u0022 has an invalid \u0022%s\u0022 value";
</span><ins>+localizedStrings["\u0022%s\u0022 is not JSON serializable"] = "\u0022%s\u0022 is not JSON serializable";
</ins><span class="cx"> localizedStrings["\u0022%s\u0022 is not valid for %s"] = "\u0022%s\u0022 is not valid for %s";
</span><span class="cx"> 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";
</span><span class="cx"> 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";
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceBaseUtilitiesjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -80,6 +80,19 @@
</span><span class="cx">     }
</span><span class="cx"> });
</span><span class="cx"> 
</span><ins>+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;
+    }
+});
+
</ins><span class="cx"> Object.defineProperty(Object.prototype, "valueForCaseInsensitiveKey",
</span><span class="cx"> {
</span><span class="cx">     value(key)
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceControllersAuditManagerjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -891,8 +891,9 @@
</span><span class="cx">                 ], {description: WI.UIString("These are all of the different test result levels.")}),
</span><span class="cx">                 new WI.AuditTestGroup(WI.UIString("Result Data"), [
</span><span class="cx">                     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.")}),
</span><del>-                    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.")}),
</del><ins>+                    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.")}),
</ins><span class="cx">                     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.")}),
</span><ins>+                    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.")}),
</ins><span class="cx">                 ], {description: WI.UIString("These are all of the different types of data that can be returned with the test result.")}),
</span><span class="cx">             ], {description: WI.UIString("These tests serve as a demonstration of the functionality and structure of audits.")}),
</span><span class="cx">             new WI.AuditTestGroup(WI.UIString("Accessibility"), [
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestBasejs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -116,7 +116,7 @@
</span><span class="cx">         } else {
</span><span class="cx">             agentCommandFunction = RuntimeAgent.evaluate;
</span><span class="cx">             agentCommandArguments.expression = `(function() { "use strict"; return eval(\`(${this._setup.replace(/`/g, "\\`")})\`)(); })()`;
</span><del>-            agentCommandArguments.objectGroup = "audit";
</del><ins>+            agentCommandArguments.objectGroup = AuditTestBase.ObjectGroup;
</ins><span class="cx">             agentCommandArguments.doNotPauseOnExceptionsAndMuteConsole = true;
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="lines">@@ -191,8 +191,8 @@
</span><span class="cx"> 
</span><span class="cx">         this._result = null;
</span><span class="cx"> 
</span><del>-        if (!options.suppressResultClearedEvent)
-            this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultCleared);
</del><ins>+        if (!options.suppressResultChangedEvent)
+            this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
</ins><span class="cx"> 
</span><span class="cx">         return true;
</span><span class="cx">     }
</span><span class="lines">@@ -228,13 +228,15 @@
</span><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> // Keep this in sync with Inspector::Protocol::Audit::VERSION.
</span><del>-WI.AuditTestBase.Version = 2;
</del><ins>+WI.AuditTestBase.Version = 3;
</ins><span class="cx"> 
</span><ins>+WI.AuditTestBase.ObjectGroup = "audit";
+
</ins><span class="cx"> WI.AuditTestBase.Event = {
</span><span class="cx">     Completed: "audit-test-base-completed",
</span><span class="cx">     DisabledChanged: "audit-test-base-disabled-changed",
</span><span class="cx">     Progress: "audit-test-base-progress",
</span><del>-    ResultCleared: "audit-test-base-result-cleared",
</del><ins>+    ResultChanged: "audit-test-base-result-changed",
</ins><span class="cx">     Scheduled: "audit-test-base-scheduled",
</span><span class="cx">     Stopping: "audit-test-base-stopping",
</span><span class="cx"> };
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestCasejs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -126,125 +126,173 @@
</span><span class="cx"> 
</span><span class="cx">         async function parseResponse(response) {
</span><span class="cx">             let remoteObject = WI.RemoteObject.fromPayload(response.result, WI.mainTarget);
</span><del>-            if (response.wasThrown || (remoteObject.type === "object" && remoteObject.subtype === "error"))
</del><ins>+            if (response.wasThrown || (remoteObject.type === "object" && remoteObject.subtype === "error")) {
</ins><span class="cx">                 addError(remoteObject.description);
</span><del>-            else if (remoteObject.type === "boolean")
</del><ins>+                return;
+            }
+
+            if (remoteObject.type === "boolean") {
</ins><span class="cx">                 setLevel(remoteObject.value ? WI.AuditTestCaseResult.Level.Pass : WI.AuditTestCaseResult.Level.Fail);
</span><del>-            else if (remoteObject.type === "string")
</del><ins>+                return;
+            }
+
+            if (remoteObject.type === "string") {
</ins><span class="cx">                 setLevel(remoteObject.value.trim().toLowerCase());
</span><del>-            else if (remoteObject.type === "object" && !remoteObject.subtype) {
-                const options = {
-                    ownProperties: true,
-                };
</del><ins>+                return;
+            }
</ins><span class="cx"> 
</span><del>-                let properties = await new Promise((resolve, reject) => remoteObject.getPropertyDescriptorsAsObject(resolve, options));
</del><ins>+            if (remoteObject.type !== "object" || remoteObject.subtype) {
+                addError(WI.UIString("Return value is not an object, string, or boolean"));
+                return;
+            }
</ins><span class="cx"> 
</span><del>-                function checkResultProperty(key, type, subtype) {
-                    if (!(key in properties))
-                        return null;
</del><ins>+            const options = {
+                ownProperties: true,
+            };
</ins><span class="cx"> 
</span><del>-                    let property = properties[key].value;
-                    if (!property)
-                        return null;
</del><ins>+            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));
+                }
</ins><span class="cx"> 
</span><del>-                    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));
-                    }
</del><ins>+                if (value.subtype !== subtype) {
+                    addErrorForValueType(subtype);
+                    return null;
+                }
</ins><span class="cx"> 
</span><del>-                    if (property.subtype !== subtype) {
-                        addErrorForValueType(subtype);
-                        return null;
-                    }
</del><ins>+                if (value.type !== type) {
+                    addErrorForValueType(type);
+                    return null;
+                }
</ins><span class="cx"> 
</span><del>-                    if (property.type !== type) {
-                        addErrorForValueType(type);
-                        return null;
-                    }
</del><ins>+                if (type === "boolean" || type === "string")
+                    return value.value;
</ins><span class="cx"> 
</span><del>-                    if (type === "boolean" || type === "string")
-                        return property.value;
</del><ins>+                return value;
+            }
</ins><span class="cx"> 
</span><del>-                    return property;
</del><ins>+            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]);
</ins><span class="cx">                 }
</span><ins>+            }
</ins><span class="cx"> 
</span><del>-                async function resultArrayForEach(key, callback) {
-                    let array = checkResultProperty(key, "object", "array");
-                    if (!array)
-                        return;
</del><ins>+            let properties = await new Promise((resolve, reject) => remoteObject.getPropertyDescriptors(resolve, options));
+            for (let property of properties) {
+                let key = property.name;
+                if (key === "__proto__")
+                    continue;
</ins><span class="cx"> 
</span><del>-                    // `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]);
-                    }
</del><ins>+                let value = property.value;
+
+                switch (key) {
+                case "level": {
+                    let levelString = checkResultProperty(key, value, "string");
+                    if (levelString)
+                        setLevel(levelString.trim().toLowerCase());
+                    break;
</ins><span class="cx">                 }
</span><span class="cx"> 
</span><del>-                let levelString = checkResultProperty("level", "string");
-                if (levelString)
-                    setLevel(levelString.trim().toLowerCase());
</del><ins>+                case "pass":
+                    if (checkResultProperty(key, value, "boolean"))
+                        setLevel(WI.AuditTestCaseResult.Level.Pass);
+                    break;
</ins><span class="cx"> 
</span><del>-                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);
</del><ins>+                case "warn":
+                    if (checkResultProperty(key, value, "boolean"))
+                        setLevel(WI.AuditTestCaseResult.Level.Warn);
+                    break;
</ins><span class="cx"> 
</span><del>-                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;
-                    }
</del><ins>+                case "fail":
+                    if (checkResultProperty(key, value, "boolean"))
+                        setLevel(WI.AuditTestCaseResult.Level.Fail);
+                    break;
</ins><span class="cx"> 
</span><del>-                    let domNodeId = await new Promise((resolve, reject) => item.value.pushNodeToFrontend(resolve));
-                    let domNode = WI.domManager.nodeForId(domNodeId);
-                    if (!domNode)
-                        return;
</del><ins>+                case "error":
+                    if (checkResultProperty(key, value, "boolean"))
+                        setLevel(WI.AuditTestCaseResult.Level.Error);
+                    break;
</ins><span class="cx"> 
</span><del>-                    if (!data.domNodes)
-                        data.domNodes = [];
-                    data.domNodes.push(WI.cssPath(domNode, {full: true}));
</del><ins>+                case "unsupported":
+                    if (checkResultProperty(key, value, "boolean"))
+                        setLevel(WI.AuditTestCaseResult.Level.Unsupported);
+                    break;
</ins><span class="cx"> 
</span><del>-                    if (!resolvedDOMNodes)
-                        resolvedDOMNodes = [];
-                    resolvedDOMNodes.push(domNode);
-                });
</del><ins>+                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;
+                        }
</ins><span class="cx"> 
</span><del>-                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;
-                    }
</del><ins>+                        let domNodeId = await new Promise((resolve, reject) => item.value.pushNodeToFrontend(resolve));
+                        let domNode = WI.domManager.nodeForId(domNodeId);
+                        if (!domNode)
+                            return;
</ins><span class="cx"> 
</span><del>-                    if (!data.domAttributes)
-                        data.domAttributes = [];
-                    data.domAttributes.push(item.value.value);
-                });
</del><ins>+                        if (!data.domNodes)
+                            data.domNodes = [];
+                        data.domNodes.push(WI.cssPath(domNode, {full: true}));
</ins><span class="cx"> 
</span><del>-                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;
-                    }
</del><ins>+                        if (!resolvedDOMNodes)
+                            resolvedDOMNodes = [];
+                        resolvedDOMNodes.push(domNode);
+                    });
+                    break;
</ins><span class="cx"> 
</span><del>-                    addError(item.value.description);
-                });
</del><ins>+                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;
+                        }
</ins><span class="cx"> 
</span><del>-                if (window.InspectorTest && properties.__test)
-                    data.__test = properties.__test.value;
-            } else
-                addError(WI.UIString("Return value is not an object, string, or boolean"));
</del><ins>+                        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;
+                }
+            }
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         let agentCommandFunction = null;
</span><span class="lines">@@ -255,7 +303,7 @@
</span><span class="cx">         } else {
</span><span class="cx">             agentCommandFunction = RuntimeAgent.evaluate;
</span><span class="cx">             agentCommandArguments.expression = `(function() { "use strict"; return eval(\`(${this._test.replace(/`/g, "\\`")})\`)(); })()`;
</span><del>-            agentCommandArguments.objectGroup = "audit";
</del><ins>+            agentCommandArguments.objectGroup = WI.AuditTestCase.ObjectGroup;
</ins><span class="cx">             agentCommandArguments.doNotPauseOnExceptionsAndMuteConsole = true;
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="lines">@@ -295,6 +343,8 @@
</span><span class="cx">         if (resolvedDOMNodes)
</span><span class="cx">             options.resolvedDOMNodes = resolvedDOMNodes;
</span><span class="cx">         this._result = new WI.AuditTestCaseResult(this.name, level, options);
</span><ins>+
+        this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
</ins><span class="cx">     }
</span><span class="cx"> };
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestCaseResultjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCaseResult.js (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -67,7 +67,7 @@
</span><span class="cx">             payload.data = {};
</span><span class="cx">         } else {
</span><span class="cx">             function checkArray(key) {
</span><del>-                if (!payload.data[key])
</del><ins>+                if (!(key in payload.data))
</ins><span class="cx">                     return;
</span><span class="cx"> 
</span><span class="cx">                 if (!Array.isArray(payload.data[key])) {
</span><span class="lines">@@ -132,24 +132,27 @@
</span><span class="cx"> 
</span><span class="cx">         if (!isEmptyObject(payload.data)) {
</span><span class="cx">             options.data = {};
</span><del>-            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;
-                    }));
</del><ins>+            for (let key in payload.data) {
+                if (key === "domNodes" || key === "domAttributes" || key === "errors") {
+                    if (!payload.data[key].length)
+                        continue;
</ins><span class="cx">                 }
</span><span class="cx"> 
</span><del>-                options.data.domNodes = payload.data.domNodes;
</del><ins>+                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];
</ins><span class="cx">             }
</span><del>-            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;
</del><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         if (!isEmptyObject(payload.metadata)) {
</span><span class="lines">@@ -210,13 +213,14 @@
</span><span class="cx">         json.level = this._level;
</span><span class="cx"> 
</span><span class="cx">         let data = {};
</span><del>-        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;
</del><ins>+        for (let key in this._data) {
+            if (key === "domNodes" || key === "domAttributes" || key === "errors") {
+                if (!this._data[key].length)
+                    continue;
+            }
+
+            data[key] = this._data[key];
</ins><span class="cx">         }
</span><del>-        if (this._data.errors && this._data.errors.length)
-            data.errors = this._data.errors;
</del><span class="cx">         if (!isEmptyObject(data))
</span><span class="cx">             json.data = data;
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestGroupjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -169,7 +169,7 @@
</span><span class="cx"> 
</span><span class="cx">         return super.clearResult({
</span><span class="cx">             ...options,
</span><del>-            suppressResultClearedEvent: !cleared,
</del><ins>+            suppressResultChangedEvent: !cleared,
</ins><span class="cx">         });
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="lines">@@ -210,6 +210,8 @@
</span><span class="cx">         this._result = new WI.AuditTestGroupResult(this.name, results, {
</span><span class="cx">             description: this.description,
</span><span class="cx">         });
</span><ins>+
+        this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _handleTestCompleted(event)
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestCaseContentViewcss"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -127,6 +127,10 @@
</span><span class="cx">     top: -1px;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.content-view.audit-test-case > section table > tr > td + td {
+    width: 100%;
+}
+
</ins><span class="cx"> .content-view.audit-test-case > section .CodeMirror {
</span><span class="cx">     width: 100%;
</span><span class="cx">     height: auto;
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestCaseContentViewjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -32,6 +32,10 @@
</span><span class="cx">         super(representedObject);
</span><span class="cx"> 
</span><span class="cx">         this.element.classList.add("audit-test-case");
</span><ins>+
+        this._resultDataGeneralContainer = null;
+        this._resultDataDOMNodesContainer = null;
+        this._resultDataErrorsContainer = null;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     // Protected
</span><span class="lines">@@ -133,14 +137,52 @@
</span><span class="cx"> 
</span><span class="cx">         let resultData = result.data;
</span><span class="cx"> 
</span><del>-        if (resultData.domNodes && resultData.domNodes.length) {
-            let domNodesContainer = this.contentView.element.appendChild(document.createElement("div"));
-            domNodesContainer.classList.add("dom-nodes");
</del><ins>+        if (!this._resultDataGeneralContainer) {
+            let nonSpecialData = Object.filter(resultData, (key) => key !== "domNodes" && key !== "errors");
+            if (!isEmptyObject(nonSpecialData)) {
+                this._resultDataGeneralContainer = document.createElement("div");
</ins><span class="cx"> 
</span><del>-            let domNodeText = domNodesContainer.appendChild(document.createElement("h1"));
</del><ins>+                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"));
</ins><span class="cx">             domNodeText.textContent = WI.UIString("DOM Nodes:");
</span><span class="cx"> 
</span><del>-            let tableContainer = domNodesContainer.appendChild(document.createElement("table"));
</del><ins>+            let tableContainer = this._resultDataDOMNodesContainer.appendChild(document.createElement("table"));
</ins><span class="cx"> 
</span><span class="cx">             resultData.domNodes.forEach((domNode, index) => {
</span><span class="cx">                 domNode = result.resolvedDOMNodes[index] || domNode;
</span><span class="lines">@@ -204,14 +246,17 @@
</span><span class="cx">             });
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        if (resultData.errors && resultData.errors.length) {
-            let errorContainer = this.contentView.element.appendChild(document.createElement("div"));
-            errorContainer.classList.add("errors");
</del><ins>+        if (this._resultDataDOMNodesContainer)
+            this.contentView.element.appendChild(this._resultDataDOMNodesContainer);
</ins><span class="cx"> 
</span><del>-            let errorText = errorContainer.appendChild(document.createElement("h1"));
</del><ins>+        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"));
</ins><span class="cx">             errorText.textContent = WI.UIString("Errors:");
</span><span class="cx"> 
</span><del>-            let tableContainer = errorContainer.appendChild(document.createElement("table"));
</del><ins>+            let tableContainer = this._resultDataErrorsContainer.appendChild(document.createElement("table"));
</ins><span class="cx"> 
</span><span class="cx">             resultData.errors.forEach((error, index) => {
</span><span class="cx">                 let rowElement = tableContainer.appendChild(document.createElement("tr"));
</span><span class="lines">@@ -227,10 +272,22 @@
</span><span class="cx">             });
</span><span class="cx">         }
</span><span class="cx"> 
</span><ins>+        if (this._resultDataErrorsContainer)
+            this.contentView.element.appendChild(this._resultDataErrorsContainer);
+
</ins><span class="cx">         if (!this.contentView.element.children.length)
</span><span class="cx">             this.showNoResultDataPlaceholder();
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    handleResultChanged(event)
+    {
+        super.handleResultChanged(event);
+
+        this._resultDataGeneralContainer = null;
+        this._resultDataDOMNodesContainer = null;
+        this._resultDataErrorsContainer = null;
+    }
+
</ins><span class="cx">     showRunningPlaceholder()
</span><span class="cx">     {
</span><span class="cx">         if (!this.placeholderElement || !this.placeholderElement.__placeholderRunning) {
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestContentViewjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -102,7 +102,7 @@
</span><span class="cx">         if (this.representedObject instanceof WI.AuditTestBase) {
</span><span class="cx">             this.representedObject.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestChanged, this);
</span><span class="cx">             this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestChanged, this);
</span><del>-            this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultCleared, this._handleTestChanged, this);
</del><ins>+            this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultChanged, this.handleResultChanged, this);
</ins><span class="cx">             this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestChanged, this);
</span><span class="cx">             this.representedObject.addEventListener(WI.AuditTestBase.Event.Stopping, this._handleTestChanged, this);
</span><span class="cx">         }
</span><span class="lines">@@ -116,6 +116,13 @@
</span><span class="cx">         super.hidden();
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    handleResultChanged(event)
+    {
+        // Overridden by sub-classes.
+
+        this.needsLayout();
+    }
+
</ins><span class="cx">     get placeholderElement()
</span><span class="cx">     {
</span><span class="cx">         return this._placeholderElement;
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTreeElementjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js (245913 => 245914)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -62,7 +62,7 @@
</span><span class="cx"> 
</span><span class="cx">         if (this.representedObject instanceof WI.AuditTestBase) {
</span><span class="cx">             this.representedObject.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
</span><del>-            this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultCleared, this._handleTestResultCleared, this);
</del><ins>+            this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultChanged, this._handleTestResultChanged, this);
</ins><span class="cx"> 
</span><span class="cx">             if (this.representedObject instanceof WI.AuditTestCase)
</span><span class="cx">                 this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestCaseScheduled, this);
</span><span class="lines">@@ -281,7 +281,7 @@
</span><span class="cx">             this._updateTestGroupDisabled();
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    _handleTestResultCleared(event)
</del><ins>+    _handleTestResultChanged(event)
</ins><span class="cx">     {
</span><span class="cx">         this._updateStatus();
</span><span class="cx">     }
</span></span></pre>
</div>
</div>

</body>
</html>