<!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>[237613] 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/237613">237613</a></dd>
<dt>Author</dt> <dd>drousso@apple.com</dd>
<dt>Date</dt> <dd>2018-10-30 18:11:36 -0700 (Tue, 30 Oct 2018)</dd>
</dl>

<h3>Log Message</h3>
<pre>Web Inspector: Audit: create Audit Tab
https://bugs.webkit.org/show_bug.cgi?id=190754

Reviewed by Matt Baker.

Source/WebInspectorUI:

Create an Audit tab for running audits on the inspected page. Leverage `Runtime.evaluate`
for running the audit tests (arbitrary JavaScript), and use the returned value to generate
a preview UI of the results. All tests/results can be exported/imported to formatted JSON:

`AuditTestCase` JSON:
    {
        "type": "test-case",
        "name": <string>,
        <optional> "description": <string>,
        "test": <stringified JavaScript function>,
    }

`AuditTestGroup` JSON:
    {
        "type": "test-group",
        "name": <string>,
        <optional> "description": <string>,
        "tests": [...<AuditTestCase, AuditTestGroup>],
    }

`AuditTestCaseResult` JSON:
    {
        "type": "test-case-result",
        "name": <string>,
        <optional> "description": <string>,
        "level": <"pass", "warn", "fail", "error", "unsupported">,
        "data": {
            "domNodes": [...<stringified CSS path>],
            "domAttributes": [...<string>],
            "errors": [...<string>],
        },
    }

`AuditTestGroupResult` JSON:
    {
        "type": "test-group-result",
        "name": <string>,
        <optional> "description": <string>,
        "results": [...<AuditTestCaseResult, AuditTestGroupResult>],
    }

More keys may be added in the future (especially for `AuditTestCaseResult.data`).

* UserInterface/Controllers/AuditManager.js:
(WI.AuditManager):
(WI.AuditManager.synthesizeError): Added.
(WI.AuditManager.prototype.get tests): Added.
(WI.AuditManager.prototype.get results): Added.
(WI.AuditManager.prototype.get runningState): Added.
(WI.AuditManager.prototype.start): Added.
(WI.AuditManager.prototype.stop): Added.
(WI.AuditManager.prototype.import): Added.
(WI.AuditManager.prototype.export): Added.
(WI.AuditManager.prototype._addTest): Added.
(WI.AuditManager.prototype._addResult): Added.
(WI.AuditManager.prototype.get testSuites): Deleted.
(WI.AuditManager.prototype.get reports): Deleted.
(WI.AuditManager.prototype.async runAuditTestByRepresentedObject): Deleted.
(WI.AuditManager.prototype.reportForId): Deleted.
(WI.AuditManager.prototype.removeAllReports): Deleted.
(WI.AuditManager.prototype.async _runTestCase): Deleted.

* UserInterface/Models/AuditTestBase.js: Added.
(WI.AuditTestBases):
(WI.AuditTestBases.prototype.get name):
(WI.AuditTestBases.prototype.get description):
(WI.AuditTestBases.prototype.get runningState):
(WI.AuditTestBases.prototype.get result):
(WI.AuditTestBases.prototype.async start):
(WI.AuditTestBases.prototype.stop):
(WI.AuditTestBases.prototype.clearResult):
(WI.AuditTestBases.prototype.saveIdentityToCookie):
(WI.AuditTestBases.prototype.toJSON):
(WI.AuditTestBases.prototype.async run):

* UserInterface/Models/AuditTestCase.js:
(WI.AuditTestCase):
(WI.AuditTestCase.fromPayload): Added.
(WI.AuditTestCase.prototype.toJSON): Added.
(WI.AuditTestCase.prototype.async run): Added.
(WI.AuditTestCase.prototype.async run.setLevel): Added.
(WI.AuditTestCase.prototype.async run.addError): Added.
(WI.AuditTestCase.prototype.async run.checkResultProperty.addErrorForValueType): Added.
(WI.AuditTestCase.prototype.async run.checkResultProperty): Added.
(WI.AuditTestCase.prototype.async run.async resultArrayForEach): Added.
(WI.AuditTestCase.prototype.get id): Deleted.
(WI.AuditTestCase.prototype.get name): Deleted.
(WI.AuditTestCase.prototype.get suite): Deleted.
(WI.AuditTestCase.prototype.get setup): Deleted.
(WI.AuditTestCase.prototype.get tearDown): Deleted.
(WI.AuditTestCase.prototype.get errorDetails): Deleted.

* UserInterface/Models/AuditTestGroup.js: Added.
(WI.AuditTestGroup):
(WI.AuditTestGroup.fromPayload):
(WI.AuditTestGroup.prototype.get tests):
(WI.AuditTestGroup.prototype.stop):
(WI.AuditTestGroup.prototype.clearResult):
(WI.AuditTestGroup.prototype.async run):
(WI.AuditTestGroup.prototype.toJSON):
(WI.AuditTestGroup.prototype._updateResult):
(WI.AuditTestGroup.prototype._handleTestCompleted):
(WI.AuditTestGroup.prototype._handleTestProgress):

* UserInterface/Models/AuditTestResultBase.js: Added.
(WI.AuditTestResultBase):
(WI.AuditTestResultBase.prototype.get name):
(WI.AuditTestResultBase.prototype.get description):
(WI.AuditTestResultBase.prototype.get result):
(WI.AuditTestResultBase.prototype.get didPass):
(WI.AuditTestResultBase.prototype.get didWarn):
(WI.AuditTestResultBase.prototype.get didFail):
(WI.AuditTestResultBase.prototype.get didError):
(WI.AuditTestResultBase.prototype.get unsupported):
(WI.AuditTestResultBase.prototype.saveIdentityToCookie):
(WI.AuditTestResultBase.prototype.toJSON):

* UserInterface/Models/AuditTestCaseResult.js: Added.
(WI.AuditTestCaseResult):
(WI.AuditTestCaseResult.fromPayload.checkArray):
(WI.AuditTestCaseResult.fromPayload):
(WI.AuditTestCaseResult.prototype.get level):
(WI.AuditTestCaseResult.prototype.get data):
(WI.AuditTestCaseResult.prototype.get didPass):
(WI.AuditTestCaseResult.prototype.get didWarn):
(WI.AuditTestCaseResult.prototype.get didFail):
(WI.AuditTestCaseResult.prototype.get didError):
(WI.AuditTestCaseResult.prototype.get unsupported):
(WI.AuditTestCaseResult.prototype.toJSON):

* UserInterface/Models/AuditTestGroupResult.js: Added.
(WI.AuditTestGroupResult):
(WI.AuditTestGroupResult.fromPayload):
(WI.AuditTestGroupResult.prototype.get results):
(WI.AuditTestGroupResult.prototype.get levelCounts):
(WI.AuditTestGroupResult.prototype.get didPass):
(WI.AuditTestGroupResult.prototype.get didWarn):
(WI.AuditTestGroupResult.prototype.get didFail):
(WI.AuditTestGroupResult.prototype.get didError):
(WI.AuditTestGroupResult.prototype.get unsupported):
(WI.AuditTestGroupResult.prototype.toJSON):

* UserInterface/Views/AuditTabContentView.js: Added.
(WI.AuditTabContentView):
(WI.AuditTabContentView.tabInfo):
(WI.AuditTabContentView.isTabAllowed):
(WI.AuditTabContentView.prototype.get type):
(WI.AuditTabContentView.prototype.get supportsSplitContentBrowser):
(WI.AuditTabContentView.prototype.canShowRepresentedObject):
(WI.AuditTabContentView.prototype.shown):
(WI.AuditTabContentView.prototype.hidden):
(WI.AuditTabContentView.prototype._handleSpace):

* UserInterface/Views/AuditNavigationSidebarPanel.js: Added.
(WI.AuditNavigationSidebarPanel):
(WI.AuditNavigationSidebarPanel.prototype.showDefaultContentView):
(WI.AuditNavigationSidebarPanel.prototype.initialLayout):
(WI.AuditNavigationSidebarPanel.prototype.closed):
(WI.AuditNavigationSidebarPanel.prototype._addTest):
(WI.AuditNavigationSidebarPanel.prototype._addResult):
(WI.AuditNavigationSidebarPanel.prototype._updateStartStopButtonNavigationItemState):
(WI.AuditNavigationSidebarPanel.prototype._handleAuditTestAdded):
(WI.AuditNavigationSidebarPanel.prototype._handleAuditTestCompleted):
(WI.AuditNavigationSidebarPanel.prototype._handleAuditTestScheduled):
(WI.AuditNavigationSidebarPanel.prototype._treeSelectionDidChange):
(WI.AuditNavigationSidebarPanel.prototype._handleStartStopButtonNavigationItemClicked):
(WI.AuditNavigationSidebarPanel.prototype._handleImportButtonNavigationItemClicked):
* UserInterface/Views/AuditNavigationSidebarPanel.css: Added.
(.sidebar > .panel.navigation.audit > .content):

* UserInterface/Views/AuditTreeElement.js: Added.
(WI.AuditTreeElement):
(WI.AuditTreeElement.prototype.get result):
(WI.AuditTreeElement.prototype.onattach):
(WI.AuditTreeElement.prototype.ondetach):
(WI.AuditTreeElement.prototype.onpopulate):
(WI.AuditTreeElement.prototype.populateContextMenu):
(WI.AuditTreeElement.prototype._start):
(WI.AuditTreeElement.prototype._updateLevel):
(WI.AuditTreeElement.prototype._showRunningSpinner):
(WI.AuditTreeElement.prototype._showRunningProgress):
(WI.AuditTreeElement.prototype._handleTestCaseCompleted):
(WI.AuditTreeElement.prototype._handleTestResultCleared):
(WI.AuditTreeElement.prototype._handleTestCaseScheduled):
(WI.AuditTreeElement.prototype._handleTestGroupCompleted):
(WI.AuditTreeElement.prototype._handleTestGroupProgress):
(WI.AuditTreeElement.prototype._handleTestGroupScheduled):
(WI.AuditTreeElement.prototype._handleStatusClick):
* UserInterface/Views/AuditTreeElement.css: Added.
(.tree-outline .item.audit > .status):
(.tree-outline .item.audit > .status > img):
(.tree-outline .item.audit:matches(.test-case, .test-group) > .status:hover > img):
(.tree-outline .item.audit > .status:not(:hover) > img.show-on-hover, .tree-outline .item.audit.test-group.expanded > .status:not(:hover)):
(.tree-outline .item.audit.test-group.expanded > .status:hover > :not(img), .tree-outline .item.audit.test-group-result.expanded > .status):
(.tree-outline .item.audit > .status > img.pass):
(.tree-outline .item.audit > .status > img.warn):
(.tree-outline .item.audit > .status > img.fail):
(.tree-outline .item.audit > .status > img.error):
(.tree-outline .item.audit > .status > img.unsupported):
(.audit.test-case .icon):
(.audit.test-group .icon):
(.audit.test-case-result .icon):
(.audit.test-group-result .icon):

* UserInterface/Views/AuditTestContentView.js: Added.
(WI.AuditTestContentView):
(WI.AuditTestContentView.prototype.get navigationItems):
(WI.AuditTestContentView.prototype.get headerView):
(WI.AuditTestContentView.prototype.get contentView):
(WI.AuditTestContentView.prototype.get supportsSave):
(WI.AuditTestContentView.prototype.get saveData):
(WI.AuditTestContentView.prototype.initialLayout):
(WI.AuditTestContentView.prototype.layout):
(WI.AuditTestContentView.prototype.shown):
(WI.AuditTestContentView.prototype.hidden):
(WI.AuditTestContentView.prototype.get placeholderElement):
(WI.AuditTestContentView.prototype.set placeholderElement):
(WI.AuditTestContentView.prototype.showRunningPlaceholder):
(WI.AuditTestContentView.prototype.showStoppingPlaceholder):
(WI.AuditTestContentView.prototype.showNoResultPlaceholder):
(WI.AuditTestContentView.prototype.showNoResultDataPlaceholder):
(WI.AuditTestContentView.prototype.showFilteredPlaceholder):
(WI.AuditTestContentView.prototype.hidePlaceholder):
(WI.AuditTestContentView.prototype.applyFilter):
(WI.AuditTestContentView.prototype.resetFilter):
(WI.AuditTestContentView.prototype._exportAudit):
(WI.AuditTestContentView.prototype._updateExportButtonNavigationItemState):
(WI.AuditTestContentView.prototype._showPlaceholder):
(WI.AuditTestContentView.prototype._handleExportButtonNavigationItemClicked):
(WI.AuditTestContentView.prototype._handleTestChanged):
* UserInterface/Views/AuditTestContentView.css: Added.
(.content-view-container > .content-view.audit-test):
(.content-view-container > .content-view.audit-test > header):
(.content-view-container > .content-view.audit-test > header h1):
(.content-view-container > .content-view.audit-test > header p):
(.content-view.audit-test):
(.content-view.audit-test h1):
(.content-view.audit-test > header):
(.content-view.audit-test > header p):
(.content-view.audit-test .audit-test.filtered, .content-view.audit-test .audit-test .message-text-view):
(.content-view.audit-test > section):
(.content-view.audit-test > section > .message-text-view):
(.content-view.audit-test.showing-placeholder):
(.content-view.audit-test.showing-placeholder > section):
(.content-view.audit-test.showing-placeholder > section > :not(.message-text-view)):
(@media (prefers-dark-interface) .content-view.audit-test):

* UserInterface/Views/AuditTestCaseContentView.js: Added.
(WI.AuditTestCaseContentView):
(WI.AuditTestCaseContentView.prototype.initialLayout):
(WI.AuditTestCaseContentView.prototype.layout):
(WI.AuditTestCaseContentView.prototype.showRunningPlaceholder):
* UserInterface/Views/AuditTestCaseContentView.css: Added.
(.content-view-container > .content-view.audit-test-case > header):
(.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view):first-child):
(.content-view.audit-test-case > header > h1):
(.content-view.audit-test-case > header > h1 > img):
(.content-view.audit-test-case > section > :not(.message-text-view)):
(.content-view.audit-test-case > section > :not(.message-text-view):last-child):
(.content-view.audit-test-case > section > :not(.message-text-view) + :not(.message-text-view)):
(.content-view.audit-test-case > section h1):
(.content-view.audit-test-case > section table):
(.content-view.audit-test-case > section table > tr + tr > td):
(.content-view.audit-test-case > section table > tr > td > :not(.tree-outline)):
(.content-view.audit-test-case > section table > tr > td:first-child):
(.content-view.audit-test-case > section > .dom-nodes > table > tr > td:first-child):
(.content-view.audit-test-case > section code):
(.content-view.audit-test-case > section mark):

* UserInterface/Views/AuditTestGroupContentView.js: Added.
(WI.AuditTestGroupContentView):
(WI.AuditTestGroupContentView.prototype.initialLayout):
(WI.AuditTestGroupContentView.prototype.layout):
(WI.AuditTestGroupContentView.prototype.shown):
(WI.AuditTestGroupContentView.prototype.hidden):
(WI.AuditTestGroupContentView.prototype.applyFilter):
(WI.AuditTestGroupContentView.prototype.resetFilter):
(WI.AuditTestGroupContentView.prototype.showRunningPlaceholder):
(WI.AuditTestGroupContentView.prototype._subobjects):
(WI.AuditTestGroupContentView.prototype._updateLevelScopeBar):
(WI.AuditTestGroupContentView.prototype._handleTestGroupCompleted):
(WI.AuditTestGroupContentView.prototype._handleTestGroupProgress):
(WI.AuditTestGroupContentView.prototype._handleTestGroupScheduled):
(WI.AuditTestGroupContentView.prototype._handleLevelScopeBarSelectionChanged):
* UserInterface/Views/AuditTestGroupContentView.css: Added.
(.content-view-container > .content-view.audit-test-group > header):
(.content-view.audit-test-group > header):
(.content-view.audit-test-group.no-matches + .audit-test-group > header):
(.content-view.audit-test-group > header, .content-view.audit-test-group:not(.filtered):last-child > header):
(.content-view.audit-test-group.contains-test-case > header):
(.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case):
(.content-view.audit-test-group.contains-test-case:not(.contains-test-group) > section, .content-view.audit-test-group.contains-test-case.contains-test-group > section > .audit-test-case):
(.content-view.audit-test-group > header > .information):
(.content-view.audit-test-group > header > .information > p):
(.content-view.audit-test-group > header > nav):
(.content-view.audit-test-group > header > nav:empty):
(.content-view.audit-test-group > header > nav:not(:empty):before):
(.content-view.audit-test-group > header > nav > .scope-bar > li):
(.content-view.audit-test-group > header > nav > .scope-bar > li:not(:hover, .selected)):
(.content-view.audit-test-group > header > nav > .scope-bar > li:last-child):
(.content-view.audit-test-group > header > nav > .scope-bar > li::before):
(.content-view.audit-test-group > header > nav > .scope-bar > li.pass::before):
(.content-view.audit-test-group > header > nav > .scope-bar > li.warn::before):
(.content-view.audit-test-group > header > nav > .scope-bar > li.fail::before):
(.content-view.audit-test-group > header > nav > .scope-bar > li.error::before):
(.content-view.audit-test-group > header > nav > .scope-bar > li.unsupported::before):
(.content-view.audit-test-group > header > .percentage-pass):
(.content-view.audit-test-group > header > .percentage-pass:not(:empty)::after):
(.content-view.audit-test-group > section > .audit-test-case:first-child, .content-view.audit-test-group > section > .audit-test-group + .audit-test-case, .content-view.audit-test-group > section > .audit-test-case + .audit-test-group):
(.content-view.audit-test-group > section > .audit-test-case:last-child):

* UserInterface/Views/ScopeBarItem.js:
(WI.ScopeBarItem):
(WI.ScopeBarItem.prototype.set selected):
* UserInterface/Views/MultipleScopeBarItem.js:
(WI.MultipleScopeBarItem.prototype.set selectedScopeBarItem):
Add an `independent` option that prevents selection changes from deselecting other
`WI.ScopeBarItem`s in the same `WI.ScopeBar` (`exclusive` takes precedence).

* UserInterface/Views/DOMTreeElement.js:
(WI.DOMTreeElement):
(WI.DOMTreeElement.prototype.highlightAttribute):
(WI.DOMTreeElement.prototype._buildAttributeDOM):
* UserInterface/Views/DOMTreeOutline.css:
(.tree-outline.dom li .highlight):

* UserInterface/Views/ToggleButtonNavigationItem.js:
(WI.ToggleButtonNavigationItem.prototype.set toggled):
Also change the `label` if the `ButtonStyle` has text.

* UserInterface/Base/Setting.js:
* UserInterface/Views/SettingsTabContentView.js:
(WI.SettingsTabContentView.prototype._createExperimentalSettingsView):

* UserInterface/Views/DividerNavigationItem.css:
(.navigation-bar .item.divider):

* UserInterface/Base/Utilities.js:
(Promise.chain): Added.

* UserInterface/Views/ContentView.js:
(WI.ContentView.createFromRepresentedObject):
(WI.ContentView.isViewable):

* UserInterface/Main.html:
* UserInterface/Base/Main.js:
(WI.loaded):
(WI.contentLoaded):

* UserInterface/Test.html:
* UserInterface/Base/Test.js:
(WI.loaded):

* UserInterface/Images/Audit.svg: Added.
* UserInterface/Images/AuditStart.svg: Added.
* UserInterface/Images/AuditStop.svg: Added.
* UserInterface/Images/AuditTestCase.svg: Added.
* UserInterface/Images/AuditTestCaseResult.svg: Added.
* UserInterface/Images/AuditTestError.svg: Added.
* UserInterface/Images/AuditTestFail.svg: Added.
* UserInterface/Images/AuditTestGroup.svg: Added.
* UserInterface/Images/AuditTestGroupResult.svg: Added.
* UserInterface/Images/AuditTestNoResult.svg: Added.
* UserInterface/Images/AuditTestPass.svg: Added.
* UserInterface/Images/AuditTestUnsupported.svg: Added.
* UserInterface/Images/AuditTestWarn.svg: Added.

* Localizations/en.lproj/localizedStrings.js:

LayoutTests:

* inspector/audit/resources/audit-utilities.js: Added.
* inspector/audit/basic-expected.txt: Added.
* inspector/audit/basic.html: Added.
* inspector/audit/data-domAttributes-expected.txt: Added.
* inspector/audit/data-domAttributes.html: Added.
* inspector/audit/data-domNodes-expected.txt: Added.
* inspector/audit/data-domNodes.html: Added.
* inspector/audit/data-errors-expected.txt: Added.
* inspector/audit/data-errors.html: Added.
* inspector/model/auditTestCase-expected.txt: Added.
* inspector/model/auditTestCase.html: Added.
* inspector/model/auditTestCaseResult-expected.txt: Added.
* inspector/model/auditTestCaseResult.html: Added.
* inspector/model/auditTestGroup-expected.txt: Added.
* inspector/model/auditTestGroup.html: Added.
* inspector/model/auditTestGroupResult-expected.txt: Added.
* inspector/model/auditTestGroupResult.html: Added.
* inspector/unit-tests/promise-utilities-expected.txt: Added.
* inspector/unit-tests/promise-utilities.html: Added.

* inspector/audit/audit-manager-expected.txt: Removed.
* inspector/audit/audit-manager.html: Removed.
* inspector/audit/audit-report-expected.txt: Removed.
* inspector/audit/audit-report.html: Removed.
* inspector/audit/audit-test-case-expected.txt: Removed.
* inspector/audit/audit-test-case.html: Removed.
* inspector/audit/audit-test-suite-expected.txt: Removed.
* inspector/audit/audit-test-suite.html: Removed.
* inspector/audit/resources/audit-test-fixtures.js: Removed.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</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="#trunkSourceWebInspectorUIUserInterfaceBaseMainjs">trunk/Source/WebInspectorUI/UserInterface/Base/Main.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceBaseSettingjs">trunk/Source/WebInspectorUI/UserInterface/Base/Setting.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="#trunkSourceWebInspectorUIUserInterfaceMainhtml">trunk/Source/WebInspectorUI/UserInterface/Main.html</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceModelsAuditTestCasejs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceTestTestjs">trunk/Source/WebInspectorUI/UserInterface/Test/Test.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceTesthtml">trunk/Source/WebInspectorUI/UserInterface/Test.html</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsContentViewjs">trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsDOMTreeElementjs">trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsDOMTreeOutlinecss">trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.css</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsDividerNavigationItemcss">trunk/Source/WebInspectorUI/UserInterface/Views/DividerNavigationItem.css</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsMaincss">trunk/Source/WebInspectorUI/UserInterface/Views/Main.css</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsScopeBarItemjs">trunk/Source/WebInspectorUI/UserInterface/Views/ScopeBarItem.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsSettingsTabContentViewjs">trunk/Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsToggleButtonNavigationItemjs">trunk/Source/WebInspectorUI/UserInterface/Views/ToggleButtonNavigationItem.js</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsinspectorauditbasicexpectedtxt">trunk/LayoutTests/inspector/audit/basic-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorauditbasichtml">trunk/LayoutTests/inspector/audit/basic.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditdatadomAttributesexpectedtxt">trunk/LayoutTests/inspector/audit/data-domAttributes-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorauditdatadomAttributeshtml">trunk/LayoutTests/inspector/audit/data-domAttributes.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditdatadomNodesexpectedtxt">trunk/LayoutTests/inspector/audit/data-domNodes-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorauditdatadomNodeshtml">trunk/LayoutTests/inspector/audit/data-domNodes.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditdataerrorsexpectedtxt">trunk/LayoutTests/inspector/audit/data-errors-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorauditdataerrorshtml">trunk/LayoutTests/inspector/audit/data-errors.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditresourcesauditutilitiesjs">trunk/LayoutTests/inspector/audit/resources/audit-utilities.js</a></li>
<li><a href="#trunkLayoutTestsinspectormodelauditTestCaseexpectedtxt">trunk/LayoutTests/inspector/model/auditTestCase-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectormodelauditTestCasehtml">trunk/LayoutTests/inspector/model/auditTestCase.html</a></li>
<li><a href="#trunkLayoutTestsinspectormodelauditTestCaseResultexpectedtxt">trunk/LayoutTests/inspector/model/auditTestCaseResult-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectormodelauditTestCaseResulthtml">trunk/LayoutTests/inspector/model/auditTestCaseResult.html</a></li>
<li><a href="#trunkLayoutTestsinspectormodelauditTestGroupexpectedtxt">trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectormodelauditTestGrouphtml">trunk/LayoutTests/inspector/model/auditTestGroup.html</a></li>
<li><a href="#trunkLayoutTestsinspectormodelauditTestGroupResultexpectedtxt">trunk/LayoutTests/inspector/model/auditTestGroupResult-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectormodelauditTestGroupResulthtml">trunk/LayoutTests/inspector/model/auditTestGroupResult.html</a></li>
<li><a href="#trunkLayoutTestsinspectorunittestspromiseutilitiesexpectedtxt">trunk/LayoutTests/inspector/unit-tests/promise-utilities-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorunittestspromiseutilitieshtml">trunk/LayoutTests/inspector/unit-tests/promise-utilities.html</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceImagesAuditsvg">trunk/Source/WebInspectorUI/UserInterface/Images/Audit.svg</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceImagesAuditStartsvg">trunk/Source/WebInspectorUI/UserInterface/Images/AuditStart.svg</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceImagesAuditStopsvg">trunk/Source/WebInspectorUI/UserInterface/Images/AuditStop.svg</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceImagesAuditTestCasesvg">trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestCase.svg</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceImagesAuditTestCaseResultsvg">trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestCaseResult.svg</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceImagesAuditTestErrorsvg">trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestError.svg</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceImagesAuditTestFailsvg">trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestFail.svg</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceImagesAuditTestGroupsvg">trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestGroup.svg</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceImagesAuditTestGroupResultsvg">trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestGroupResult.svg</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceImagesAuditTestNoResultsvg">trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestNoResult.svg</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceImagesAuditTestPasssvg">trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestPass.svg</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceImagesAuditTestUnsupportedsvg">trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestUnsupported.svg</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceImagesAuditTestWarnsvg">trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestWarn.svg</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceModelsAuditTestBasejs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.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="#trunkSourceWebInspectorUIUserInterfaceModelsAuditTestGroupResultjs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroupResult.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceModelsAuditTestResultBasejs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestResultBase.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsAuditNavigationSidebarPanelcss">trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.css</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsAuditNavigationSidebarPaneljs">trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsAuditTabContentViewjs">trunk/Source/WebInspectorUI/UserInterface/Views/AuditTabContentView.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="#trunkSourceWebInspectorUIUserInterfaceViewsAuditTestContentViewcss">trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.css</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsAuditTestContentViewjs">trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsAuditTestGroupContentViewcss">trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.css</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsAuditTestGroupContentViewjs">trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsAuditTreeElementcss">trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.css</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsAuditTreeElementjs">trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js</a></li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsinspectorauditauditmanagerexpectedtxt">trunk/LayoutTests/inspector/audit/audit-manager-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorauditauditmanagerhtml">trunk/LayoutTests/inspector/audit/audit-manager.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditauditreportexpectedtxt">trunk/LayoutTests/inspector/audit/audit-report-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorauditauditreporthtml">trunk/LayoutTests/inspector/audit/audit-report.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditaudittestcaseexpectedtxt">trunk/LayoutTests/inspector/audit/audit-test-case-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorauditaudittestcasehtml">trunk/LayoutTests/inspector/audit/audit-test-case.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditaudittestsuiteexpectedtxt">trunk/LayoutTests/inspector/audit/audit-test-suite-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorauditaudittestsuitehtml">trunk/LayoutTests/inspector/audit/audit-test-suite.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditresourcesaudittestfixturesjs">trunk/LayoutTests/inspector/audit/resources/audit-test-fixtures.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceModelsAuditReportjs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditReport.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceModelsAuditResultjs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditResult.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceModelsAuditTestSuitejs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestSuite.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog      2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/LayoutTests/ChangeLog 2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,3 +1,40 @@
</span><ins>+2018-10-30  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Audit: create Audit Tab
+        https://bugs.webkit.org/show_bug.cgi?id=190754
+
+        Reviewed by Matt Baker.
+
+        * inspector/audit/resources/audit-utilities.js: Added.
+        * inspector/audit/basic-expected.txt: Added.
+        * inspector/audit/basic.html: Added.
+        * inspector/audit/data-domAttributes-expected.txt: Added.
+        * inspector/audit/data-domAttributes.html: Added.
+        * inspector/audit/data-domNodes-expected.txt: Added.
+        * inspector/audit/data-domNodes.html: Added.
+        * inspector/audit/data-errors-expected.txt: Added.
+        * inspector/audit/data-errors.html: Added.
+        * inspector/model/auditTestCase-expected.txt: Added.
+        * inspector/model/auditTestCase.html: Added.
+        * inspector/model/auditTestCaseResult-expected.txt: Added.
+        * inspector/model/auditTestCaseResult.html: Added.
+        * inspector/model/auditTestGroup-expected.txt: Added.
+        * inspector/model/auditTestGroup.html: Added.
+        * inspector/model/auditTestGroupResult-expected.txt: Added.
+        * inspector/model/auditTestGroupResult.html: Added.
+        * inspector/unit-tests/promise-utilities-expected.txt: Added.
+        * inspector/unit-tests/promise-utilities.html: Added.
+
+        * inspector/audit/audit-manager-expected.txt: Removed.
+        * inspector/audit/audit-manager.html: Removed.
+        * inspector/audit/audit-report-expected.txt: Removed.
+        * inspector/audit/audit-report.html: Removed.
+        * inspector/audit/audit-test-case-expected.txt: Removed.
+        * inspector/audit/audit-test-case.html: Removed.
+        * inspector/audit/audit-test-suite-expected.txt: Removed.
+        * inspector/audit/audit-test-suite.html: Removed.
+        * inspector/audit/resources/audit-test-fixtures.js: Removed.
+
</ins><span class="cx"> 2018-10-30  Dawei Fenton  <realdawei@apple.com>
</span><span class="cx"> 
</span><span class="cx">         WebGL conformance: Failures and Timeouts in suite 2.0.0/conformance
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditauditmanagerexpectedtxt"></a>
<div class="delfile"><h4>Deleted: trunk/LayoutTests/inspector/audit/audit-manager-expected.txt (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/audit-manager-expected.txt     2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/LayoutTests/inspector/audit/audit-manager-expected.txt        2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,41 +0,0 @@
</span><del>-Test for the AuditManager Instantiation.
-
-
-== Running test suite: AuditManager
--- Running test case: Adding an AuditTestSuite
-PASS: AuditManager should have 0 testSuite.
-Adding an AuditTestSuite to AuditManager.
-PASS: AuditManager should have 1 test suite.
-PASS: New test suite has the correct name.
-PASS: New test suite is of AuditTestSuite.
-
--- Running test case: Adding a duplicating AuditTestSuite
-PASS: Should produce an exception.
-Error: class testSuiteFixture1 already exists.
-
--- Running test case: Perform tests by AuditTestSuite.
-PASS: Receive a report that is of instance AuditReport.
-PASS: AuditReport is not writable
-PASS: There are two results in AuditReport.
-PASS: auditResults 0  is an instance of AuditResult.
-PASS: auditReport 0 is expected for test case fakeTest1.
-PASS: auditResults 1  is an instance of AuditResult.
-PASS: auditReport 1 is expected for test case fakeTest2.
-AuditReport is not writable.
-Attempting to add another AuditResult to AuditReport.
-PASS: AuditReport no longer accepts new AuditResults.
-PASS: Report represents the expected AuditTestSuite.
-
--- Running test case: Perform a test by AuditTestCase.
-PASS: Receive a report that is of instance AuditReport.
-PASS: AuditReport represents the expected AuditTestCase.
-
--- Running test case: AuditReports are unique.
-Only the most recent AuditReport for a case/suite is retained.
-PASS: The report represents the correct AuditTestSuite.
-
--- Running test case: Get AuditReport by AuditTestCase/Suite id.
-Running a test for an AuditTestSuite and an AuditTestCase.
-PASS: The report represents the correct AuditTestSuite.
-PASS: The report represents the correct AuditTestCase.
-
</del></span></pre></div>
<a id="trunkLayoutTestsinspectorauditauditmanagerhtml"></a>
<div class="delfile"><h4>Deleted: trunk/LayoutTests/inspector/audit/audit-manager.html (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/audit-manager.html     2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/LayoutTests/inspector/audit/audit-manager.html        2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,139 +0,0 @@
</span><del>-<!doctype html>
-<html>
-<head>
-<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
-<script src="./resources/audit-test-fixtures.js"></script>
-<script>
-function test()
-{
-    let suite = InspectorTest.createAsyncSuite("AuditManager");
-
-    suite.addTestCase({
-        name: "Adding an AuditTestSuite",
-        description: "AuditManager should have one instantiated AuditTestSuite.",
-        async test(){
-            let auditManager = new WI.AuditManager;
-
-            InspectorTest.expectThat(!auditManager.testSuites.length, "AuditManager should have 0 testSuite.");
-
-            InspectorTest.log("Adding an AuditTestSuite to AuditManager.");
-            auditManager.addTestSuite(testSuiteFixture1);
-            let testSuite = auditManager.testSuites[0];
-
-            InspectorTest.expectEqual(auditManager.testSuites.length, 1, "AuditManager should have 1 test suite.");
-            InspectorTest.expectEqual(testSuite.name, "FakeTestSuite1", "New test suite has the correct name.");
-            InspectorTest.expectThat(testSuite instanceof WI.AuditTestSuite, "New test suite is of AuditTestSuite.");
-        }
-    });
-
-    suite.addTestCase({
-        name: "Adding a duplicating AuditTestSuite",
-        description: "Should throw exception for duplicated test suite.",
-        async test() {
-            let auditManager = new WI.AuditManager;
-
-            auditManager.addTestSuite(testSuiteFixture1);
-            auditManager.addTestSuite(testSuiteFixture2);
-
-            InspectorTest.expectException(() => {
-               auditManager.addTestSuite(testSuiteFixture1);
-            });
-        }
-    });
-
-    suite.addTestCase({
-        name: "Perform tests by AuditTestSuite.",
-        description: "Should produce report for AuditTestSuite.",
-        async test() {
-            let auditManager = new WI.AuditManager;
-
-            InspectorTest.assert(!auditManager._reports.size, "auditManager has no reports.");
-
-            let testSuite = new testSuiteFixture1;
-            let testCaseNames = testSuite.testCases.map(testCase => {
-                return testCase.name;
-            });
-            
-            await auditManager.runAuditTestByRepresentedObject(testSuite);
-
-            let auditReport = auditManager.reports[0];
-
-            InspectorTest.expectThat(auditReport instanceof WI.AuditReport, "Receive a report that is of instance AuditReport."); 
-            InspectorTest.expectThat(!auditReport._isWritable, "AuditReport is not writable");
-            InspectorTest.expectEqual(auditReport.resultsData.length, 2, "There are two results in AuditReport.");
-            
-            for (let i = 0; i < auditReport.resultsData.length; i++) {
-                let resultToTest = auditReport.resultsData[i];
-                InspectorTest.expectThat(resultToTest instanceof WI.AuditResult, `auditResults ${i}  is an instance of AuditResult.`)
-                InspectorTest.expectThat(testCaseNames.indexOf(resultToTest.name) >= 0, `auditReport ${i} is expected for test case ${resultToTest.name}.`);
-            }
-            InspectorTest.log("AuditReport is not writable.");
-            InspectorTest.log("Attempting to add another AuditResult to AuditReport.");
-            let additionalReport = auditReport.resultsData[1];
-            auditReport.addResult(additionalReport);
-
-            InspectorTest.expectEqual(auditReport.resultsData.length, 2, "AuditReport no longer accepts new AuditResults.");
-            InspectorTest.expectEqual(auditReport.representedTestSuite.id, testSuite.id, "Report represents the expected AuditTestSuite.");
-        }
-    });
-
-
-    suite.addTestCase({
-        name: "Perform a test by AuditTestCase.",
-        description: "Should produce report for AuditTestCase.",
-        async test() {
-            let auditManager = new WI.AuditManager;
-            auditManager.addTestSuite(testSuiteFixture1);
-            let testCase = auditManager.testSuites[0].testCases[0];
-
-            await auditManager.runAuditTestByRepresentedObject(testCase);
-
-            let auditReport = auditManager.reports[0];
-
-            InspectorTest.expectThat(auditReport instanceof WI.AuditReport, "Receive a report that is of instance AuditReport.");
-            InspectorTest.expectEqual(auditReport.representedTestCases[0], testCase, "AuditReport represents the expected AuditTestCase.");
-        }
-    });
-
-    suite.addTestCase({
-        name: "AuditReports are unique.",
-        description: "No AuditReport should represent the same AuditTestCase/Suite",
-        async test() {
-            let auditManager = new WI.AuditManager;
-            let testSuite = new testSuiteFixture1;
-
-            InspectorTest.log("Only the most recent AuditReport for a case/suite is retained.");
-
-            let results = [await auditManager.runAuditTestByRepresentedObject(testSuite), await auditManager.runAuditTestByRepresentedObject(testSuite)];
-
-            InspectorTest.expectEqual(results[0].representedTestSuite, results[1].representedTestSuite, "The report represents the correct AuditTestSuite.");
-        }
-    });
-
-    suite.addTestCase({
-        name: "Get AuditReport by AuditTestCase/Suite id.",
-        description: "Should return the correct AuditReport.",
-        async test() {
-            let auditManager = new WI.AuditManager;
-            let testSuite = new testSuiteFixture1;
-            let testCase = testSuite.testCases[0];
-
-            InspectorTest.log("Running a test for an AuditTestSuite and an AuditTestCase.");
-            let results = [await auditManager.runAuditTestByRepresentedObject(testSuite), await auditManager.runAuditTestByRepresentedObject(testCase)];
-
-            let auditReportForTestSuite = auditManager.reportForId(testSuite.id);
-            let auditReportForTestCase = auditManager.reportForId(testCase.id);
-
-            InspectorTest.expectEqual(results[0], auditReportForTestSuite, "The report represents the correct AuditTestSuite.");
-            InspectorTest.expectEqual(results[1], auditReportForTestCase, "The report represents the correct AuditTestCase.");
-        }
-    });
-
-    suite.runTestCasesAndFinish();
-}
-</script>
-</head>
-<body onload="runTest()">
-<p>Test for the AuditManager Instantiation.</p>
-</body>
-</html>
</del></span></pre></div>
<a id="trunkLayoutTestsinspectorauditauditreportexpectedtxt"></a>
<div class="delfile"><h4>Deleted: trunk/LayoutTests/inspector/audit/audit-report-expected.txt (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/audit-report-expected.txt      2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/LayoutTests/inspector/audit/audit-report-expected.txt 2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,7 +0,0 @@
</span><del>-Test for the AuditManager Instantiation.
-
-
-== Running test suite: AuditReport
--- Running test case: Instantiation with test suite
-PASS: Instantiate AuditReport with AuditTestSuite.
-
</del></span></pre></div>
<a id="trunkLayoutTestsinspectorauditauditreporthtml"></a>
<div class="delfile"><h4>Deleted: trunk/LayoutTests/inspector/audit/audit-report.html (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/audit-report.html      2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/LayoutTests/inspector/audit/audit-report.html 2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,29 +0,0 @@
</span><del>-<!doctype html>
-<html>
-<head>
-<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
-<script src="resources/audit-test-fixtures.js"></script>
-<script>
-function test()
-{
-    let suite = InspectorTest.createAsyncSuite("AuditReport");
-
-    suite.addTestCase({
-        name: "Instantiation with test suite",
-        description: "should instantiate correctly.",
-        async test() {
-            let testSuite = new testSuiteFixture1;
-            InspectorTest.assert(testSuite instanceof WI.AuditTestSuite, "testSuite is AuditTestSuite.");
-            let report = new WI.AuditReport(testSuite);
-            InspectorTest.expectThat(report instanceof WI.AuditReport, "Instantiate AuditReport with AuditTestSuite.");
-        }
-    });
-
-    suite.runTestCasesAndFinish();
-}
-</script>
-</head>
-<body onload="runTest()">
-<p>Test for the AuditManager Instantiation.</p>
-</body>
-</html>
</del></span></pre></div>
<a id="trunkLayoutTestsinspectorauditaudittestcaseexpectedtxt"></a>
<div class="delfile"><h4>Deleted: trunk/LayoutTests/inspector/audit/audit-test-case-expected.txt (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/audit-test-case-expected.txt   2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/LayoutTests/inspector/audit/audit-test-case-expected.txt      2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,8 +0,0 @@
</span><del>-Test for the AudtTestCase.
-
-
-== Running test suite: AuditTestCase
--- Running test case: Test functions must be asynchronous.
-PASS: Should produce an exception.
-Error: Test functions must be async functions.
-
</del></span></pre></div>
<a id="trunkLayoutTestsinspectorauditaudittestcasehtml"></a>
<div class="delfile"><h4>Deleted: trunk/LayoutTests/inspector/audit/audit-test-case.html (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/audit-test-case.html   2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/LayoutTests/inspector/audit/audit-test-case.html      2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,28 +0,0 @@
</span><del>-<!doctype html>
-<html>
-<head>
-<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
-<script src="./resources/audit-test-fixtures.js"></script>
-<script>
-function test()
-{
-    let suite = InspectorTest.createAsyncSuite("AuditTestCase");
-
-    suite.addTestCase({
-        name: "Test functions must be asynchronous.",
-        description: "AuditTestCase should throw an exception when instantiated with a non-async function.",
-        async test() {
-            InspectorTest.expectException(() => {
-                new WI.AuditTestCase(new testSuiteFixture1, "fakeTest2", () => []);
-            });
-        }
-    })
-
-    suite.runTestCasesAndFinish();
-}
-</script>
-</head>
-<body onload="runTest()">
-<p>Test for the AudtTestCase.</p>
-</body>
-</html>
</del></span></pre></div>
<a id="trunkLayoutTestsinspectorauditaudittestsuiteexpectedtxt"></a>
<div class="delfile"><h4>Deleted: trunk/LayoutTests/inspector/audit/audit-test-suite-expected.txt (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/audit-test-suite-expected.txt  2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/LayoutTests/inspector/audit/audit-test-suite-expected.txt     2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,19 +0,0 @@
</span><del>-Test for the AuditTestSuite.
-
-
-== Running test suite: AuditTestSuite
--- Running test case: AuditTestSuite Id
-PASS: AuditTestSuite1 has ID with correct type.
-PASS: AuditTestSuite2 has ID with correct type.
-PASS: AuditTestSuites with same name have different unique IDs.
-
--- Running test case: AuditTestSuite testCaseCount
-PASS: There are two tests.
-
--- Running test case: AuditTestSuite should run tests sequentially.
-PASS: First test is ran.
-PASS: Second test is ran.
-PASS: Third test is ran.
-PASS: Fourth test is ran.
-PASS: Last test is ran.
-
</del></span></pre></div>
<a id="trunkLayoutTestsinspectorauditaudittestsuitehtml"></a>
<div class="delfile"><h4>Deleted: trunk/LayoutTests/inspector/audit/audit-test-suite.html (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/audit-test-suite.html  2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/LayoutTests/inspector/audit/audit-test-suite.html     2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,106 +0,0 @@
</span><del>-<!doctype html>
-<html>
-<head>
-<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
-<script src="./resources/audit-test-fixtures.js"></script>
-<script>
-function test()
-{
-
-    let suite = InspectorTest.createAsyncSuite("AuditTestSuite");
-
-    let auditTestSuite1 = new testSuiteFixture1("FakeTestSuite", "FakeTestSuite");
-    let auditTestSuite2 = new testSuiteFixture1("FakeTestSuite", "FakeTestSuite");
-
-    suite.addTestCase({
-        name: "AuditTestSuite Id",
-        description: "Should exist and be unique",
-        async test() {
-            InspectorTest.expectEqual(typeof(auditTestSuite1.id), "symbol", "AuditTestSuite1 has ID with correct type.");
-            InspectorTest.expectEqual(typeof(auditTestSuite2.id), "symbol", "AuditTestSuite2 has ID with correct type.");
-            InspectorTest.expectThat(auditTestSuite1.id !== auditTestSuite2.id, "AuditTestSuites with same name have different unique IDs.");
-        }
-    });
-
-    suite.addTestCase({
-        name: "AuditTestSuite testCaseCount",
-        description: "Should represents correct number of test case.",
-        async test() {
-            InspectorTest.expectEqual(auditTestSuite1.testCases.length, 2, "There are two tests.");
-        }
-    });
-
-
-
-    suite.addTestCase({
-        name: "AuditTestSuite should run tests sequentially.",
-        description: "Tests should be ran in the order that was defined in the testDescriptor.",
-        async test() {
-                
-            let testOrderSymbol = Symbol("test-order");
-            window[testOrderSymbol] = 0;
-
-            let TestOrderSuite = class TestOrderSuite extends WI.AuditTestSuite 
-            {
-                static testCaseDescriptors()
-                {
-                    return [
-                        {
-                            name: "test 1",
-                            description: "order 1",
-                            async test() {
-                                InspectorTest.expectEqual(window[testOrderSymbol], 0, "First test is ran.");
-                                window[testOrderSymbol] = 1;
-                            }
-                        },
-                        {
-                            name: "test 2",
-                            description: "order 2",
-                            async test() {
-                                InspectorTest.expectEqual(window[testOrderSymbol], 1, "Second test is ran.");
-                                window[testOrderSymbol] = 2;
-                            }
-                        },
-                        {
-                            name: "test 3",
-                            description: "order 3",
-                            async test() {
-                                InspectorTest.expectEqual(window[testOrderSymbol], 2, "Third test is ran.");
-                                window[testOrderSymbol] = 3;
-                            }
-                        },
-                        {
-                            name: "test 4",
-                            description: "order 4",
-                            async test() {
-                                InspectorTest.expectEqual(window[testOrderSymbol], 3, "Fourth test is ran.");
-                                window[testOrderSymbol] = 4;
-                            }
-                        },
-                        {
-                            name: "test 4",
-                            description: "order 4",
-                            async test() {
-                                InspectorTest.expectEqual(window[testOrderSymbol], 4, "Last test is ran.");
-                            }
-                        }
-                    ]
-                }
-            }
-
-            let auditManager = new WI.AuditManager;
-            auditManager.addTestSuite(TestOrderSuite);
-
-            let result = auditManager.runAuditTestByRepresentedObject(auditManager.testSuites[0]);

-        }
-    });
-
-    suite.runTestCasesAndFinish();
-}
-</script>
-</head>
-<body onload="runTest()">
-<p>Test for the AuditTestSuite.</p>
-</body>
-</html>
</del></span></pre></div>
<a id="trunkLayoutTestsinspectorauditbasicexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/audit/basic-expected.txt (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/basic-expected.txt                             (rev 0)
+++ trunk/LayoutTests/inspector/audit/basic-expected.txt        2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,88 @@
</span><ins>+Testing the basic functionality of audits.
+
+
+== Running test suite: Audit.Basic
+-- Running test case: Audit.Basic.Boolean.True
+Testing value `true`...
+PASS: Result should be "pass".
+
+-- Running test case: Audit.Basic.Boolean.False
+Testing value `false`...
+PASS: Result should be "fail".
+
+-- Running test case: Audit.Basic.String.Pass
+Testing value `"pass"`...
+PASS: Result should be "pass".
+
+-- Running test case: Audit.Basic.String.Warn
+Testing value `"warn"`...
+PASS: Result should be "warn".
+
+-- Running test case: Audit.Basic.String.Fail
+Testing value `"fail"`...
+PASS: Result should be "fail".
+
+-- Running test case: Audit.Basic.String.Error
+Testing value `"error"`...
+PASS: Result should be "error".
+
+-- Running test case: Audit.Basic.String.Unsupported
+Testing value `"unsupported"`...
+PASS: Result should be "unsupported".
+
+-- Running test case: Audit.Basic.Object.Pass
+Testing value `{"level":"pass"}`...
+PASS: Result should be "pass".
+
+-- Running test case: Audit.Basic.Object.Warn
+Testing value `{"level":"warn"}`...
+PASS: Result should be "warn".
+
+-- Running test case: Audit.Basic.Object.Fail
+Testing value `{"level":"fail"}`...
+PASS: Result should be "fail".
+
+-- Running test case: Audit.Basic.Object.Error
+Testing value `{"level":"error"}`...
+PASS: Result should be "error".
+
+-- Running test case: Audit.Basic.Object.Unsupported
+Testing value `{"level":"unsupported"}`...
+PASS: Result should be "unsupported".
+
+-- Running test case: Audit.Basic.Error.Undefined
+Testing...
+PASS: Result should be "error".
+  errors:
+   - TypeError: eval(undefined) is not a function. (In 'eval(undefined)()', 'eval(undefined)' is undefined)
+
+-- Running test case: Audit.Basic.Error.Null
+Testing...
+PASS: Result should be "error".
+  errors:
+   - TypeError: eval(null) is not a function. (In 'eval(null)()', 'eval(null)' is null)
+
+-- Running test case: Audit.Basic.Error.Number
+Testing...
+PASS: Result should be "error".
+  errors:
+   - TypeError: eval(42) is not a function. (In 'eval(42)()', 'eval(42)' is 42)
+
+-- Running test case: Audit.Basic.Error.String
+Testing value `"foo"`...
+PASS: Result should be "error".
+  errors:
+   - Return string must be one of ["pass","warn","fail","error","unsupported"]
+
+-- Running test case: Audit.Basic.Error.Object
+Testing value `{}`...
+PASS: Result should be "error".
+  errors:
+   - Missing result level
+
+-- Running test case: Audit.Basic.Error.Variable
+Testing...
+PASS: Result should be "error".
+  errors:
+   - ReferenceError: Can't find variable: INVALID
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectorauditbasichtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/audit/basic.html (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/basic.html                             (rev 0)
+++ trunk/LayoutTests/inspector/audit/basic.html        2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,40 @@
</span><ins>+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script src="resources/audit-utilities.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.Audit.createSuite("Audit.Basic");
+
+    InspectorTest.Audit.addFunctionlessTest("Audit.Basic.Boolean.True", true, WI.AuditTestCaseResult.Level.Pass);
+    InspectorTest.Audit.addFunctionlessTest("Audit.Basic.Boolean.False", false, WI.AuditTestCaseResult.Level.Fail);
+
+    InspectorTest.Audit.addStringTest("Audit.Basic.String.Pass", WI.AuditTestCaseResult.Level.Pass, WI.AuditTestCaseResult.Level.Pass);
+    InspectorTest.Audit.addStringTest("Audit.Basic.String.Warn", WI.AuditTestCaseResult.Level.Warn, WI.AuditTestCaseResult.Level.Warn);
+    InspectorTest.Audit.addStringTest("Audit.Basic.String.Fail", WI.AuditTestCaseResult.Level.Fail, WI.AuditTestCaseResult.Level.Fail);
+    InspectorTest.Audit.addStringTest("Audit.Basic.String.Error", WI.AuditTestCaseResult.Level.Error, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addStringTest("Audit.Basic.String.Unsupported", WI.AuditTestCaseResult.Level.Unsupported, WI.AuditTestCaseResult.Level.Unsupported);
+
+    InspectorTest.Audit.addObjectTest("Audit.Basic.Object.Pass", {level: WI.AuditTestCaseResult.Level.Pass}, WI.AuditTestCaseResult.Level.Pass);
+    InspectorTest.Audit.addObjectTest("Audit.Basic.Object.Warn", {level: WI.AuditTestCaseResult.Level.Warn}, WI.AuditTestCaseResult.Level.Warn);
+    InspectorTest.Audit.addObjectTest("Audit.Basic.Object.Fail", {level: WI.AuditTestCaseResult.Level.Fail}, WI.AuditTestCaseResult.Level.Fail);
+    InspectorTest.Audit.addObjectTest("Audit.Basic.Object.Error", {level: WI.AuditTestCaseResult.Level.Error}, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addObjectTest("Audit.Basic.Object.Unsupported", {level: WI.AuditTestCaseResult.Level.Unsupported}, WI.AuditTestCaseResult.Level.Unsupported);
+
+    InspectorTest.Audit.addTest("Audit.Basic.Error.Undefined", undefined, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addTest("Audit.Basic.Error.Null", null, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addTest("Audit.Basic.Error.Number", 42, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addStringTest("Audit.Basic.Error.String", "foo", WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addObjectTest("Audit.Basic.Error.Object", {}, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addTest("Audit.Basic.Error.Variable", "INVALID", WI.AuditTestCaseResult.Level.Error);
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Testing the basic functionality of audits.</p>
+</body>
+</html>
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectorauditdatadomAttributesexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/audit/data-domAttributes-expected.txt (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/data-domAttributes-expected.txt                                (rev 0)
+++ trunk/LayoutTests/inspector/audit/data-domAttributes-expected.txt   2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,39 @@
</span><ins>+Testing audits involving DOM attributes.
+
+
+== Running test suite: Audit.DOMAttributes
+-- Running test case: Audit.DOMAttributes.Valid
+Testing value `{"level":"pass","domAttributes":["id","tabindex"]}`...
+PASS: Result should be "pass".
+  domAttributes:
+   - id
+   - tabindex
+
+-- Running test case: Audit.DOMAttributes.Undefined
+Testing value `{"level":"pass"}`...
+PASS: Result should be "pass".
+
+-- Running test case: Audit.DOMAttributes.Error.Null
+Testing value `{"level":"pass","domAttributes":null}`...
+PASS: Result should be "error".
+  errors:
+   - “domAttributes“ must be an array
+
+-- Running test case: Audit.DOMAttributes.Error.Number
+Testing value `{"level":"pass","domAttributes":42}`...
+PASS: Result should be "error".
+  errors:
+   - “domAttributes“ must be an array
+
+-- Running test case: Audit.DOMAttributes.Error.String
+Testing value `{"level":"pass","domAttributes":"foo"}`...
+PASS: Result should be "error".
+  errors:
+   - “domAttributes“ must be an array
+
+-- Running test case: Audit.DOMAttributes.Error.Object
+Testing value `{"level":"pass","domAttributes":{}}`...
+PASS: Result should be "error".
+  errors:
+   - “domAttributes“ must be an array
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectorauditdatadomAttributeshtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/audit/data-domAttributes.html (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/data-domAttributes.html                                (rev 0)
+++ trunk/LayoutTests/inspector/audit/data-domAttributes.html   2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,27 @@
</span><ins>+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script src="resources/audit-utilities.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.Audit.createSuite("Audit.DOMAttributes");
+
+    InspectorTest.Audit.addObjectTest("Audit.DOMAttributes.Valid", {level: WI.AuditTestCaseResult.Level.Pass, domAttributes: ["id", "tabindex"]}, WI.AuditTestCaseResult.Level.Pass);
+
+    InspectorTest.Audit.addObjectTest("Audit.DOMAttributes.Undefined", {level: WI.AuditTestCaseResult.Level.Pass, domAttributes: undefined}, WI.AuditTestCaseResult.Level.Pass);
+
+    InspectorTest.Audit.addObjectTest("Audit.DOMAttributes.Error.Null", {level: WI.AuditTestCaseResult.Level.Pass, domAttributes: null}, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addObjectTest("Audit.DOMAttributes.Error.Number", {level: WI.AuditTestCaseResult.Level.Pass, domAttributes: 42}, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addObjectTest("Audit.DOMAttributes.Error.String", {level: WI.AuditTestCaseResult.Level.Pass, domAttributes: "foo"}, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addObjectTest("Audit.DOMAttributes.Error.Object", {level: WI.AuditTestCaseResult.Level.Pass, domAttributes: {}}, WI.AuditTestCaseResult.Level.Error);
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Testing audits involving DOM attributes.</p>
+</body>
+</html>
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectorauditdatadomNodesexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/audit/data-domNodes-expected.txt (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/data-domNodes-expected.txt                             (rev 0)
+++ trunk/LayoutTests/inspector/audit/data-domNodes-expected.txt        2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,57 @@
</span><ins>+Testing audits involving DOM nodes.
+
+
+== Running test suite: Audit.DOMNodes
+-- Running test case: Audit.DOMNodes.Tag
+Testing selector `div`...
+PASS: Result should be "fail".
+  domNodes:
+   - div#id1.class2.class3
+   - div#id2.class3.class1
+   - div#id3.class1.class2
+
+-- Running test case: Audit.DOMNodes.ID
+Testing selector `#id1`...
+PASS: Result should be "fail".
+  domNodes:
+   - div#id1.class2.class3
+
+-- Running test case: Audit.DOMNodes.ClassName
+Testing selector `.class1`...
+PASS: Result should be "fail".
+  domNodes:
+   - div#id2.class3.class1
+   - div#id3.class1.class2
+
+-- Running test case: Audit.DOMNodes.DoesNotExist
+Testing selector `DoesNotExist`...
+PASS: Result should be "pass".
+
+-- Running test case: Audit.DOMNodes.Undefined
+Testing value `{"level":"pass"}`...
+PASS: Result should be "pass".
+
+-- Running test case: Audit.DOMNodes.Error.Null
+Testing value `{"level":"pass","domNodes":null}`...
+PASS: Result should be "error".
+  errors:
+   - “domNodes“ must be an array
+
+-- Running test case: Audit.DOMNodes.Error.Number
+Testing value `{"level":"pass","domNodes":42}`...
+PASS: Result should be "error".
+  errors:
+   - “domNodes“ must be an array
+
+-- Running test case: Audit.DOMNodes.Error.String
+Testing value `{"level":"pass","domNodes":"foo"}`...
+PASS: Result should be "error".
+  errors:
+   - “domNodes“ must be an array
+
+-- Running test case: Audit.DOMNodes.Error.Object
+Testing value `{"level":"pass","domNodes":{}}`...
+PASS: Result should be "error".
+  errors:
+   - “domNodes“ must be an array
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectorauditdatadomNodeshtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/audit/data-domNodes.html (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/data-domNodes.html                             (rev 0)
+++ trunk/LayoutTests/inspector/audit/data-domNodes.html        2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,33 @@
</span><ins>+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script src="resources/audit-utilities.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.Audit.createSuite("Audit.DOMNodes");
+
+    InspectorTest.Audit.addDOMSelectorTest("Audit.DOMNodes.Tag", "div", WI.AuditTestCaseResult.Level.Fail);
+    InspectorTest.Audit.addDOMSelectorTest("Audit.DOMNodes.ID", "#id1", WI.AuditTestCaseResult.Level.Fail);
+    InspectorTest.Audit.addDOMSelectorTest("Audit.DOMNodes.ClassName", ".class1", WI.AuditTestCaseResult.Level.Fail);
+    InspectorTest.Audit.addDOMSelectorTest("Audit.DOMNodes.DoesNotExist", "DoesNotExist", WI.AuditTestCaseResult.Level.Pass);
+
+    InspectorTest.Audit.addObjectTest("Audit.DOMNodes.Undefined", {level: WI.AuditTestCaseResult.Level.Pass, domNodes: undefined}, WI.AuditTestCaseResult.Level.Pass);
+
+    InspectorTest.Audit.addObjectTest("Audit.DOMNodes.Error.Null", {level: WI.AuditTestCaseResult.Level.Pass, domNodes: null}, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addObjectTest("Audit.DOMNodes.Error.Number", {level: WI.AuditTestCaseResult.Level.Pass, domNodes: 42}, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addObjectTest("Audit.DOMNodes.Error.String", {level: WI.AuditTestCaseResult.Level.Pass, domNodes: "foo"}, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addObjectTest("Audit.DOMNodes.Error.Object", {level: WI.AuditTestCaseResult.Level.Pass, domNodes: {}}, WI.AuditTestCaseResult.Level.Error);
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Testing audits involving DOM nodes.</p>
+    <div id="id1" class="class2 class3"></div>
+    <div id="id2" class="class3 class1"></div>
+    <div id="id3" class="class1 class2"></div>
+</body>
+</html>
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectorauditdataerrorsexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/audit/data-errors-expected.txt (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/data-errors-expected.txt                               (rev 0)
+++ trunk/LayoutTests/inspector/audit/data-errors-expected.txt  2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,62 @@
</span><ins>+Testing audits involving DOM attributes.
+
+
+== Running test suite: Audit.Errors
+-- Running test case: Audit.Errors.MissingVariable
+Testing value `y`...
+PASS: Result should be "error".
+  errors:
+   - ReferenceError: Can't find variable: y
+
+-- Running test case: Audit.Errors.UndefinedAsObject
+Testing value `x.x.x`...
+PASS: Result should be "error".
+  errors:
+   - TypeError: undefined is not an object (evaluating 'x.x.x')
+
+-- Running test case: Audit.Errors.NotAFunction
+Testing value `x()`...
+PASS: Result should be "error".
+  errors:
+   - TypeError: x is not a function. (In 'x()', 'x' is an instance of Object)
+
+-- Running test case: Audit.Errors.InvalidLevel
+Testing value `{"level":"INVALID"}`...
+PASS: Result should be "error".
+  errors:
+   - Return string must be one of ["pass","warn","fail","error","unsupported"]
+
+-- Running test case: Audit.Errors.UserGenerated
+Testing value `{"level":"pass","errors":["user generated error"]}`...
+PASS: Result should be "error".
+  errors:
+   - All items in “errors“ must be error objects
+
+-- Running test case: Audit.Errors.Undefined
+Testing value `{"level":"pass"}`...
+PASS: Result should be "pass".
+
+-- Running test case: Audit.Errors.Error.Null
+Testing value `{"level":"pass","errors":null}`...
+PASS: Result should be "error".
+  errors:
+   - “errors“ must be an array
+
+-- Running test case: Audit.Errors.Error.Number
+Testing value `{"level":"pass","errors":42}`...
+PASS: Result should be "error".
+  errors:
+   - “errors“ must be an array
+
+-- Running test case: Audit.Errors.Error.String
+Testing value `{"level":"pass","errors":"foo"}`...
+PASS: Result should be "error".
+  errors:
+   - “errors“ must be an array
+
+-- Running test case: Audit.Errors.Error.Object
+Testing value `{"level":"pass","errors":{}}`...
+PASS: Result should be "error".
+  errors:
+   - “errors“ must be an array
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectorauditdataerrorshtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/audit/data-errors.html (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/data-errors.html                               (rev 0)
+++ trunk/LayoutTests/inspector/audit/data-errors.html  2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,37 @@
</span><ins>+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script src="resources/audit-utilities.js"></script>
+<script>
+
+let x = {};
+
+function test()
+{
+    let suite = InspectorTest.Audit.createSuite("Audit.Errors");
+
+    InspectorTest.Audit.addFunctionlessTest("Audit.Errors.MissingVariable", "y", WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addFunctionlessTest("Audit.Errors.UndefinedAsObject", "x.x.x", WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addFunctionlessTest("Audit.Errors.NotAFunction", "x()", WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addObjectTest("Audit.Errors.InvalidLevel", {level: "INVALID"}, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addObjectTest("Audit.Errors.UserGenerated", {level: WI.AuditTestCaseResult.Level.Pass, errors: ["user generated error"]}, WI.AuditTestCaseResult.Level.Error);
+
+    InspectorTest.Audit.addObjectTest("Audit.Errors.Undefined", {level: WI.AuditTestCaseResult.Level.Pass, errors: undefined}, WI.AuditTestCaseResult.Level.Pass);
+
+    InspectorTest.Audit.addObjectTest("Audit.Errors.Error.Null", {level: WI.AuditTestCaseResult.Level.Pass, errors: null}, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addObjectTest("Audit.Errors.Error.Number", {level: WI.AuditTestCaseResult.Level.Pass, errors: 42}, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addObjectTest("Audit.Errors.Error.String", {level: WI.AuditTestCaseResult.Level.Pass, errors: "foo"}, WI.AuditTestCaseResult.Level.Error);
+    InspectorTest.Audit.addObjectTest("Audit.Errors.Error.Object", {level: WI.AuditTestCaseResult.Level.Pass, errors: {}}, WI.AuditTestCaseResult.Level.Error);
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Testing audits involving DOM attributes.</p>
+    <div id="id1" class="class2 class3"></div>
+    <div id="id2" class="class3 class1"></div>
+    <div id="id3" class="class1 class2"></div>
+</body>
+</html>
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectorauditresourcesaudittestfixturesjs"></a>
<div class="delfile"><h4>Deleted: trunk/LayoutTests/inspector/audit/resources/audit-test-fixtures.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/resources/audit-test-fixtures.js       2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/LayoutTests/inspector/audit/resources/audit-test-fixtures.js  2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,49 +0,0 @@
</span><del>-TestPage.registerInitializer(() => {
-
-    window.testSuiteFixture1 = class testSuiteFixture1 extends WI.AuditTestSuite
-    {
-        constructor()
-        {
-            super("FakeTestSuite1", "FakeTestSuite1");
-        }
-
-        static testCaseDescriptors()
-        {
-            return [
-                {
-                    name: "fakeTest1",
-                    async test(){}
-                },
-                {
-                    name: "fakeTest2",
-                    async test()
-                    {
-                        throw new Error([1, 2, 3, 4]);
-                    }
-                }
-            ];
-        }
-    }
-
-    window.testSuiteFixture2 = class testSuiteFixture2 extends WI.AuditTestSuite
-    {
-        constructor()
-        {
-            super("FakeTestSuite2", "FakeTestSuite2");
-        }
-
-        static testCaseDescriptors()
-        {
-            return [
-                {
-                    name: "fakeTest2",
-                    async test()
-                    {
-                        return [];
-                    }
-                }
-            ];
-        }
-    }
-  
-});
</del></span></pre></div>
<a id="trunkLayoutTestsinspectorauditresourcesauditutilitiesjs"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/audit/resources/audit-utilities.js (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/resources/audit-utilities.js                           (rev 0)
+++ trunk/LayoutTests/inspector/audit/resources/audit-utilities.js      2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,85 @@
</span><ins>+TestPage.registerInitializer(() => {
+    const querySelectorTest = `function() {
+    let domNodes = Array.from(document.querySelectorAll("%s"));
+    return {
+        level: domNodes.length ? "fail" : "pass",
+        domNodes,
+    };
+}`;
+
+    let suite = null;
+
+    function logArray(name, array) {
+        if (!array.length)
+            return;
+
+        InspectorTest.assert(array.every((item) => typeof item === "string"), name + "should only contain strings.");
+        InspectorTest.log("  " + name + ":");
+        for (let item of array)
+            InspectorTest.log("   - " + item);
+    }
+
+    InspectorTest.Audit = {};
+
+    InspectorTest.Audit.createSuite = function(name) {
+        suite = InspectorTest.createAsyncSuite(name);
+        return suite;
+    }
+
+    InspectorTest.Audit.addTest = function(name, test, level, logs = {}) {
+        suite.addTestCase({
+            name,
+            test(resolve, reject) {
+                let audit = new WI.AuditTestCase(name, test);
+
+                WI.auditManager.awaitEvent(WI.AuditManager.Event.TestCompleted).then((event) => {
+                    let results = WI.auditManager.results.lastValue;
+                    InspectorTest.assert(results.length === 1, "There should be 1 result.");
+
+                    let result = results[0];
+                    InspectorTest.assert(result instanceof WI.AuditTestCaseResult, "Result should be a WI.AuditTestCaseResult.");
+                    if (!result)
+                        return;
+
+                    InspectorTest.expectEqual(result.level, level, `Result should be "${level}".`);
+
+                    let data = result.data;
+                    if (data.domNodes) {
+                        InspectorTest.assert(data.domNodes.every((domNode) => domNode instanceof WI.DOMNode), "domNodes should only contain WI.DOMNode.");
+                        logArray("domNodes", data.domNodes.map((domNode) => domNode.displayName));
+                    }
+                    if (data.domAttributes)
+                        logArray("domAttributes", data.domAttributes);
+                    if (data.errors)
+                        logArray("errors", data.errors);
+                })
+                .then(resolve, reject);
+
+                InspectorTest.log("Testing" + (logs.beforeStart || "") + "...");
+
+                WI.auditManager.start([audit])
+                .then(resolve, reject);
+            },
+        });
+    };
+
+    InspectorTest.Audit.addFunctionlessTest = function(name, test, level) {
+        InspectorTest.Audit.addTest(name, `function() { return ${test} }`, level, {
+            beforeStart: ` value \`${test}\``,
+        });
+    };
+
+    InspectorTest.Audit.addStringTest = function(name, test, level) {
+        InspectorTest.Audit.addFunctionlessTest(name, `"${test}"`, level);
+    };
+
+    InspectorTest.Audit.addObjectTest = function(name, test, level) {
+        InspectorTest.Audit.addFunctionlessTest(name, JSON.stringify(test), level);
+    };
+
+    InspectorTest.Audit.addDOMSelectorTest = function(name, test, level) {
+        InspectorTest.Audit.addTest(name, querySelectorTest.format(test), level, {
+            beforeStart: ` selector \`${test}\``,
+        });
+    };
+});
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectormodelauditTestCaseexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/model/auditTestCase-expected.txt (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/model/auditTestCase-expected.txt                             (rev 0)
+++ trunk/LayoutTests/inspector/model/auditTestCase-expected.txt        2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,38 @@
</span><ins>+Testing the functions of WI.AuditTestCase.
+
+
+== Running test suite: AuditTestCase
+-- Running test case: AuditTestCase.fromPayload.nullObject
+null
+
+-- Running test case: AuditTestCase.fromPayload.nonObject
+null
+
+-- Running test case: AuditTestCase.fromPayload.emptyObject
+null
+
+-- Running test case: AuditTestCase.fromPayload.invalidTopLevelMembers
+null
+
+-- Running test case: AuditTestCase.fromPayload.valid
+{
+  "type": "test-case",
+  "name": "valid test name",
+  "test": "function() { }"
+}
+
+-- Running test case: AuditTestCase.fromPayload.validWithInvalidOptionals
+{
+  "type": "test-case",
+  "name": "validWithInvalidOptionals test name",
+  "test": "validWithInvalidOptionals test function"
+}
+
+-- Running test case: AuditTestCase.fromPayload.validWithValidOptionals
+{
+  "type": "test-case",
+  "name": "validWithValidOptionals test name",
+  "description": "validWithValidOptionals test description",
+  "test": "validWithValidOptionals test function"
+}
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectormodelauditTestCasehtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/model/auditTestCase.html (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/model/auditTestCase.html                             (rev 0)
+++ trunk/LayoutTests/inspector/model/auditTestCase.html        2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,77 @@
</span><ins>+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("AuditTestCase");
+
+    function addPayloadTest({name, payload}) {
+        suite.addTestCase({
+            name,
+            async test() {
+                let object = WI.AuditTestCase.fromPayload(payload);
+                InspectorTest.log(object ? JSON.stringify(object, null, 2) : object);
+            },
+        });
+    }
+
+    let payloadTests = [
+        {
+            name: "AuditTestCase.fromPayload.nullObject",
+            payload: null,
+        },
+        {
+            name: "AuditTestCase.fromPayload.nonObject",
+            payload: "INVALID",
+        },
+        {
+            name: "AuditTestCase.fromPayload.emptyObject",
+            payload: {},
+        },
+        {
+            name: "AuditTestCase.fromPayload.invalidTopLevelMembers",
+            payload: {
+                type: null,
+                name: null,
+                test: null,
+            },
+        },
+        {
+            name: "AuditTestCase.fromPayload.valid",
+            payload: {
+                type: WI.AuditTestCase.TypeIdentifier,
+                name: "valid test name",
+                test: "function() { }",
+            },
+        },
+        {
+            name: "AuditTestCase.fromPayload.validWithInvalidOptionals",
+            payload: {
+                type: WI.AuditTestCase.TypeIdentifier,
+                name: "validWithInvalidOptionals test name",
+                description: null,
+                test: "validWithInvalidOptionals test function",
+            },
+        },
+        {
+            name: "AuditTestCase.fromPayload.validWithValidOptionals",
+            payload: {
+                type: WI.AuditTestCase.TypeIdentifier,
+                name: "validWithValidOptionals test name",
+                description: "validWithValidOptionals test description",
+                test: "validWithValidOptionals test function",
+            },
+        },
+    ];
+    payloadTests.forEach(addPayloadTest);
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Testing the functions of WI.AuditTestCase.</p>
+</body>
+</html>
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectormodelauditTestCaseResultexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/model/auditTestCaseResult-expected.txt (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/model/auditTestCaseResult-expected.txt                               (rev 0)
+++ trunk/LayoutTests/inspector/model/auditTestCaseResult-expected.txt  2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,57 @@
</span><ins>+Testing the functions of WI.AuditTestCaseResult.
+
+
+== Running test suite: AuditTestCaseResult
+-- Running test case: AuditTestCaseResult.fromPayload.nullObject
+null
+
+-- Running test case: AuditTestCaseResult.fromPayload.nonObject
+null
+
+-- Running test case: AuditTestCaseResult.fromPayload.emptyObject
+null
+
+-- Running test case: AuditTestCaseResult.fromPayload.invalidTopLevelMembers
+null
+
+-- Running test case: AuditTestCaseResult.fromPayload.valid
+{
+  "type": "test-case-result",
+  "name": "valid test result name",
+  "level": "pass"
+}
+
+-- Running test case: AuditTestCaseResult.fromPayload.validWithInvalidOptionals
+{
+  "type": "test-case-result",
+  "name": "validWithInvalidOptionals test result name",
+  "level": "pass"
+}
+
+-- Running test case: AuditTestCaseResult.fromPayload.validWithInvalidSubOptionals
+{
+  "type": "test-case-result",
+  "name": "validWithInvalidSubOptionals test result name",
+  "description": "validWithInvalidSubOptionals test result description",
+  "level": "pass"
+}
+
+-- Running test case: AuditTestCaseResult.fromPayload.validWithValidSubOptionals
+{
+  "type": "test-case-result",
+  "name": "validWithValidSubOptionals test result name",
+  "description": "validWithValidSubOptionals test result description",
+  "level": "pass",
+  "data": {
+    "domNodes": [
+      "validWithValidSubOptionals test result domNode"
+    ],
+    "domAttributes": [
+      "validWithValidSubOptionals test result domAttribute"
+    ],
+    "errors": [
+      "validWithValidSubOptionals test result error"
+    ]
+  }
+}
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectormodelauditTestCaseResulthtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/model/auditTestCaseResult.html (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/model/auditTestCaseResult.html                               (rev 0)
+++ trunk/LayoutTests/inspector/model/auditTestCaseResult.html  2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,97 @@
</span><ins>+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("AuditTestCaseResult");
+
+    function addPayloadTest({name, payload}) {
+        suite.addTestCase({
+            name,
+            async test() {
+                let object = WI.AuditTestCaseResult.fromPayload(payload);
+                InspectorTest.log(object ? JSON.stringify(object, null, 2) : object);
+            },
+        });
+    }
+
+    let payloadTests = [
+        {
+            name: "AuditTestCaseResult.fromPayload.nullObject",
+            payload: null,
+        },
+        {
+            name: "AuditTestCaseResult.fromPayload.nonObject",
+            payload: "INVALID",
+        },
+        {
+            name: "AuditTestCaseResult.fromPayload.emptyObject",
+            payload: {},
+        },
+        {
+            name: "AuditTestCaseResult.fromPayload.invalidTopLevelMembers",
+            payload: {
+                type: null,
+                name: null,
+                level: null,
+            },
+        },
+        {
+            name: "AuditTestCaseResult.fromPayload.valid",
+            payload: {
+                type: WI.AuditTestCaseResult.TypeIdentifier,
+                name: "valid test result name",
+                level: WI.AuditTestCaseResult.Level.Pass,
+            },
+        },
+        {
+            name: "AuditTestCaseResult.fromPayload.validWithInvalidOptionals",
+            payload: {
+                type: WI.AuditTestCaseResult.TypeIdentifier,
+                name: "validWithInvalidOptionals test result name",
+                description: null,
+                level: WI.AuditTestCaseResult.Level.Pass,
+                data: null,
+            },
+        },
+        {
+            name: "AuditTestCaseResult.fromPayload.validWithInvalidSubOptionals",
+            payload: {
+                type: WI.AuditTestCaseResult.TypeIdentifier,
+                name: "validWithInvalidSubOptionals test result name",
+                description: "validWithInvalidSubOptionals test result description",
+                level: WI.AuditTestCaseResult.Level.Pass,
+                data: {
+                    domNodes: null,
+                    domAttributes: null,
+                    errors: null,
+                },
+            },
+        },
+        {
+            name: "AuditTestCaseResult.fromPayload.validWithValidSubOptionals",
+            payload: {
+                type: WI.AuditTestCaseResult.TypeIdentifier,
+                name: "validWithValidSubOptionals test result name",
+                description: "validWithValidSubOptionals test result description",
+                level: WI.AuditTestCaseResult.Level.Pass,
+                data: {
+                    domNodes: ["validWithValidSubOptionals test result domNode"],
+                    domAttributes: ["validWithValidSubOptionals test result domAttribute"],
+                    errors: ["validWithValidSubOptionals test result error"],
+                },
+            },
+        },
+    ];
+    payloadTests.forEach(addPayloadTest);
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Testing the functions of WI.AuditTestCaseResult.</p>
+</body>
+</html>
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectormodelauditTestGroupexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt                            (rev 0)
+++ trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt       2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,91 @@
</span><ins>+Testing the functions of WI.AuditTestGroup.
+
+
+== Running test suite: AuditTestGroup
+-- Running test case: AuditTestGroup.fromPayload.nullObject
+null
+
+-- Running test case: AuditTestGroup.fromPayload.nonObject
+null
+
+-- Running test case: AuditTestGroup.fromPayload.emptyObject
+null
+
+-- Running test case: AuditTestGroup.fromPayload.invalidTopLevelMembers
+null
+
+-- Running test case: AuditTestGroup.fromPayload.missingSubMembers
+null
+
+-- Running test case: AuditTestGroup.fromPayload.invalidSubMembers
+null
+
+-- Running test case: AuditTestGroup.fromPayload.valid
+{
+  "type": "test-group",
+  "name": "valid group name",
+  "tests": [
+    {
+      "type": "test-case",
+      "name": "valid test name",
+      "test": "valid test function"
+    }
+  ]
+}
+
+-- Running test case: AuditTestGroup.fromPayload.validWithInvalidOptionals
+{
+  "type": "test-group",
+  "name": "validWithInvalidOptionals group name",
+  "tests": [
+    {
+      "type": "test-case",
+      "name": "validWithInvalidOptionals test name",
+      "test": "validWithInvalidOptionals test function"
+    }
+  ]
+}
+
+-- Running test case: AuditTestGroup.fromPayload.validWithValidOptionals
+{
+  "type": "test-group",
+  "name": "validWithValidOptionals group name",
+  "description": "validWithValidOptionals group description",
+  "tests": [
+    {
+      "type": "test-case",
+      "name": "validWithValidOptionals test name",
+      "description": "validWithValidOptionals test description",
+      "test": "validWithValidOptionals test function"
+    }
+  ]
+}
+
+-- Running test case: AuditTestGroup.fromPayload.validNested
+{
+  "type": "test-group",
+  "name": "validNested group name",
+  "description": "validNested group description",
+  "tests": [
+    {
+      "type": "test-group",
+      "name": "validNested nested group name",
+      "description": "validNested nested group description",
+      "tests": [
+        {
+          "type": "test-case",
+          "name": "validNested nested test name",
+          "description": "validNested nested test description",
+          "test": "validNested nested test function"
+        }
+      ]
+    },
+    {
+      "type": "test-case",
+      "name": "validNested test name",
+      "description": "validNested test description",
+      "test": "validNested test function"
+    }
+  ]
+}
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectormodelauditTestGrouphtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/model/auditTestGroup.html (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/model/auditTestGroup.html                            (rev 0)
+++ trunk/LayoutTests/inspector/model/auditTestGroup.html       2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,144 @@
</span><ins>+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("AuditTestGroup");
+
+    function addPayloadTest({name, payload}) {
+        suite.addTestCase({
+            name,
+            async test() {
+                let object = WI.AuditTestGroup.fromPayload(payload);
+                InspectorTest.log(object ? JSON.stringify(object, null, 2) : object);
+            },
+        });
+    }
+
+    let payloadTests = [
+        {
+            name: "AuditTestGroup.fromPayload.nullObject",
+            payload: null,
+        },
+        {
+            name: "AuditTestGroup.fromPayload.nonObject",
+            payload: "INVALID",
+        },
+        {
+            name: "AuditTestGroup.fromPayload.emptyObject",
+            payload: {},
+        },
+        {
+            name: "AuditTestGroup.fromPayload.invalidTopLevelMembers",
+            payload: {
+                type: null,
+                name: null,
+                tests: null,
+            },
+        },
+        {
+            name: "AuditTestGroup.fromPayload.missingSubMembers",
+            payload: {
+                type: WI.AuditTestGroup.TypeIdentifier,
+                name: "missingSubMembers group name",
+                tests: [],
+            },
+        },
+        {
+            name: "AuditTestGroup.fromPayload.invalidSubMembers",
+            payload: {
+                type: WI.AuditTestGroup.TypeIdentifier,
+                name: "invalidSubMembers group name",
+                tests: [
+                    null,
+                ],
+            },
+        },
+        {
+            name: "AuditTestGroup.fromPayload.valid",
+            payload: {
+                type: WI.AuditTestGroup.TypeIdentifier,
+                name: "valid group name",
+                tests: [
+                    {
+                        type: WI.AuditTestCase.TypeIdentifier,
+                        name: "valid test name",
+                        test: "valid test function",
+                    },
+                ],
+            },
+        },
+        {
+            name: "AuditTestGroup.fromPayload.validWithInvalidOptionals",
+            payload: {
+                type: WI.AuditTestGroup.TypeIdentifier,
+                name: "validWithInvalidOptionals group name",
+                description: null,
+                tests: [
+                    {
+                        type: WI.AuditTestCase.TypeIdentifier,
+                        name: "validWithInvalidOptionals test name",
+                        description: null,
+                        test: "validWithInvalidOptionals test function",
+                    },
+                ],
+            },
+        },
+        {
+            name: "AuditTestGroup.fromPayload.validWithValidOptionals",
+            payload: {
+                type: WI.AuditTestGroup.TypeIdentifier,
+                name: "validWithValidOptionals group name",
+                description: "validWithValidOptionals group description",
+                tests: [
+                    {
+                        type: WI.AuditTestCase.TypeIdentifier,
+                        name: "validWithValidOptionals test name",
+                        description: "validWithValidOptionals test description",
+                        test: "validWithValidOptionals test function",
+                    },
+                ],
+            },
+        },
+        {
+            name: "AuditTestGroup.fromPayload.validNested",
+            payload: {
+                type: WI.AuditTestGroup.TypeIdentifier,
+                name: "validNested group name",
+                description: "validNested group description",
+                tests: [
+                    {
+                        type: WI.AuditTestGroup.TypeIdentifier,
+                        name: "validNested nested group name",
+                        description: "validNested nested group description",
+                        tests: [
+                            {
+                                type: WI.AuditTestCase.TypeIdentifier,
+                                name: "validNested nested test name",
+                                description: "validNested nested test description",
+                                test: "validNested nested test function",
+                            },
+                        ],
+                    },
+                    {
+                        type: WI.AuditTestCase.TypeIdentifier,
+                        name: "validNested test name",
+                        description: "validNested test description",
+                        test: "validNested test function",
+                    },
+                ],
+            },
+        },
+    ];
+    payloadTests.forEach(addPayloadTest);
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Testing the functions of WI.AuditTestGroup.</p>
+</body>
+</html>
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectormodelauditTestGroupResultexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/model/auditTestGroupResult-expected.txt (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/model/auditTestGroupResult-expected.txt                              (rev 0)
+++ trunk/LayoutTests/inspector/model/auditTestGroupResult-expected.txt 2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,124 @@
</span><ins>+Testing the functions of WI.AuditTestGroupResult.
+
+
+== Running test suite: AuditTestGroupResult
+-- Running test case: AuditTestGroupResult.fromPayload.nullObject
+null
+
+-- Running test case: AuditTestGroupResult.fromPayload.nonObject
+null
+
+-- Running test case: AuditTestGroupResult.fromPayload.emptyObject
+null
+
+-- Running test case: AuditTestGroupResult.fromPayload.invalidTopLevelMembers
+null
+
+-- Running test case: AuditTestGroupResult.fromPayload.missingSubMembers
+null
+
+-- Running test case: AuditTestGroupResult.fromPayload.invalidSubMembers
+null
+
+-- Running test case: AuditTestGroupResult.fromPayload.valid
+{
+  "type": "test-group-result",
+  "name": "valid group result name",
+  "results": [
+    {
+      "type": "test-case-result",
+      "name": "valid test result name",
+      "level": "pass"
+    }
+  ]
+}
+
+-- Running test case: AuditTestGroupResult.fromPayload.validWithInvalidOptionals
+{
+  "type": "test-group-result",
+  "name": "validWithInvalidOptionals group result name",
+  "results": [
+    {
+      "type": "test-case-result",
+      "name": "validWithInvalidOptionals test result name",
+      "level": "pass"
+    }
+  ]
+}
+
+-- Running test case: AuditTestGroupResult.fromPayload.validWithValidOptionals
+{
+  "type": "test-group-result",
+  "name": "validWithValidOptionals group result name",
+  "description": "validWithValidOptionals group result description",
+  "results": [
+    {
+      "type": "test-case-result",
+      "name": "validWithValidOptionals test result name",
+      "description": "validWithValidOptionals test result description",
+      "level": "pass",
+      "data": {
+        "domNodes": [
+          "validWithValidOptionals test result domNode"
+        ],
+        "domAttributes": [
+          "validWithValidOptionals test result domAttribute"
+        ],
+        "errors": [
+          "validWithValidOptionals test result error"
+        ]
+      }
+    }
+  ]
+}
+
+-- Running test case: AuditTestGroupResult.fromPayload.validNested
+{
+  "type": "test-group-result",
+  "name": "validNested group result name",
+  "description": "validNested group result description",
+  "results": [
+    {
+      "type": "test-group-result",
+      "name": "validNested nested group result name",
+      "description": "validNested nested group result description",
+      "results": [
+        {
+          "type": "test-case-result",
+          "name": "validNested nested test result name",
+          "description": "validNested nested test result description",
+          "level": "pass",
+          "data": {
+            "domNodes": [
+              "validNested nested test result domNode"
+            ],
+            "domAttributes": [
+              "validNested nested test result domAttribute"
+            ],
+            "errors": [
+              "validNested nested test result error"
+            ]
+          }
+        }
+      ]
+    },
+    {
+      "type": "test-case-result",
+      "name": "validNested test result name",
+      "description": "validNested test result description",
+      "level": "pass",
+      "data": {
+        "domNodes": [
+          "validNested test result domNode"
+        ],
+        "domAttributes": [
+          "validNested test result domAttribute"
+        ],
+        "errors": [
+          "validNested test result error"
+        ]
+      }
+    }
+  ]
+}
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectormodelauditTestGroupResulthtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/model/auditTestGroupResult.html (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/model/auditTestGroupResult.html                              (rev 0)
+++ trunk/LayoutTests/inspector/model/auditTestGroupResult.html 2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,160 @@
</span><ins>+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("AuditTestGroupResult");
+
+    function addPayloadTest({name, payload}) {
+        suite.addTestCase({
+            name,
+            async test() {
+                let object = WI.AuditTestGroupResult.fromPayload(payload);
+                InspectorTest.log(object ? JSON.stringify(object, null, 2) : object);
+            },
+        });
+    }
+
+    let payloadTests = [
+        {
+            name: "AuditTestGroupResult.fromPayload.nullObject",
+            payload: null,
+        },
+        {
+            name: "AuditTestGroupResult.fromPayload.nonObject",
+            payload: "INVALID",
+        },
+        {
+            name: "AuditTestGroupResult.fromPayload.emptyObject",
+            payload: {},
+        },
+        {
+            name: "AuditTestGroupResult.fromPayload.invalidTopLevelMembers",
+            payload: {
+                type: null,
+                name: null,
+                results: null,
+            },
+        },
+        {
+            name: "AuditTestGroupResult.fromPayload.missingSubMembers",
+            payload: {
+                type: WI.AuditTestGroupResult.TypeIdentifier,
+                name: "missingSubMembers group result name",
+                results: [],
+            },
+        },
+        {
+            name: "AuditTestGroupResult.fromPayload.invalidSubMembers",
+            payload: {
+                type: WI.AuditTestGroupResult.TypeIdentifier,
+                name: "invalidSubMembers group result name",
+                results: [
+                    null,
+                ],
+            },
+        },
+        {
+            name: "AuditTestGroupResult.fromPayload.valid",
+            payload: {
+                type: WI.AuditTestGroupResult.TypeIdentifier,
+                name: "valid group result name",
+                results: [
+                    {
+                        type: WI.AuditTestCaseResult.TypeIdentifier,
+                        name: "valid test result name",
+                        level: WI.AuditTestCaseResult.Level.Pass,
+                    },
+                ],
+            },
+        },
+        {
+            name: "AuditTestGroupResult.fromPayload.validWithInvalidOptionals",
+            payload: {
+                type: WI.AuditTestGroupResult.TypeIdentifier,
+                name: "validWithInvalidOptionals group result name",
+                description: null,
+                results: [
+                    {
+                        type: WI.AuditTestCaseResult.TypeIdentifier,
+                        name: "validWithInvalidOptionals test result name",
+                        description: null,
+                        level: WI.AuditTestCaseResult.Level.Pass,
+                        data: null,
+                    },
+                ],
+            },
+        },
+        {
+            name: "AuditTestGroupResult.fromPayload.validWithValidOptionals",
+            payload: {
+                type: WI.AuditTestGroupResult.TypeIdentifier,
+                name: "validWithValidOptionals group result name",
+                description: "validWithValidOptionals group result description",
+                results: [
+                    {
+                        type: WI.AuditTestCaseResult.TypeIdentifier,
+                        name: "validWithValidOptionals test result name",
+                        description: "validWithValidOptionals test result description",
+                        level: WI.AuditTestCaseResult.Level.Pass,
+                        data: {
+                            domNodes: ["validWithValidOptionals test result domNode"],
+                            domAttributes: ["validWithValidOptionals test result domAttribute"],
+                            errors: ["validWithValidOptionals test result error"],
+                        },
+                    },
+                ],
+            },
+        },
+        {
+            name: "AuditTestGroupResult.fromPayload.validNested",
+            payload: {
+                type: WI.AuditTestGroupResult.TypeIdentifier,
+                name: "validNested group result name",
+                description: "validNested group result description",
+                results: [
+                    {
+                        type: WI.AuditTestGroupResult.TypeIdentifier,
+                        name: "validNested nested group result name",
+                        description: "validNested nested group result description",
+                        results: [
+                            {
+                                type: WI.AuditTestCaseResult.TypeIdentifier,
+                                name: "validNested nested test result name",
+                                description: "validNested nested test result description",
+                                level: WI.AuditTestCaseResult.Level.Pass,
+                                data: {
+                                    domNodes: ["validNested nested test result domNode"],
+                                    domAttributes: ["validNested nested test result domAttribute"],
+                                    errors: ["validNested nested test result error"],
+                                },
+                            },
+                        ],
+                    },
+                    {
+                        type: WI.AuditTestCaseResult.TypeIdentifier,
+                        name: "validNested test result name",
+                        description: "validNested test result description",
+                        level: WI.AuditTestCaseResult.Level.Pass,
+                        data: {
+                            domNodes: ["validNested test result domNode"],
+                            domAttributes: ["validNested test result domAttribute"],
+                            errors: ["validNested test result error"],
+                        },
+                    },
+                ],
+            },
+        },
+    ];
+    payloadTests.forEach(addPayloadTest);
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Testing the functions of WI.AuditTestGroupResult.</p>
+</body>
+</html>
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectorunittestspromiseutilitiesexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/unit-tests/promise-utilities-expected.txt (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/unit-tests/promise-utilities-expected.txt                            (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/promise-utilities-expected.txt       2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,7 @@
</span><ins>+
+== Running test suite: Promise
+-- Running test case: Promise.chain
+PASS: Resolved promise 0 with value 1
+PASS: Resolved promise 1 with value 2
+PASS: Resolved promise 2 with value 4
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectorunittestspromiseutilitieshtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/unit-tests/promise-utilities.html (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/unit-tests/promise-utilities.html                            (rev 0)
+++ trunk/LayoutTests/inspector/unit-tests/promise-utilities.html       2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,34 @@
</span><ins>+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("Promise");
+
+    suite.addTestCase({
+        name: "Promise.chain",
+        async test() {
+            function createPromise(lastValue, i) {
+                return new Promise((resolve, reject) => {
+                    InspectorTest.pass(`Resolved promise ${i} with value ${lastValue}`);
+                    resolve(lastValue * 2);
+                });
+            }
+
+            let results = await Promise.chain([createPromise, createPromise, createPromise], 1);
+            InspectorTest.assert(results[0] === 2, "Result 1 should be 2 (1 * 2)");
+            InspectorTest.assert(results[1] === 4, "Result 2 should be 4 (1 * 2 * 2)");
+            InspectorTest.assert(results[2] === 8, "Result 3 should be 8 (1 * 2 * 2 * 2)");
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onLoad="runTest()">
+</body>
+</html>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/ChangeLog (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/ChangeLog    2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/ChangeLog       2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,5 +1,381 @@
</span><span class="cx"> 2018-10-30  Devin Rousso  <drousso@apple.com>
</span><span class="cx"> 
</span><ins>+        Web Inspector: Audit: create Audit Tab
+        https://bugs.webkit.org/show_bug.cgi?id=190754
+
+        Reviewed by Matt Baker.
+
+        Create an Audit tab for running audits on the inspected page. Leverage `Runtime.evaluate`
+        for running the audit tests (arbitrary JavaScript), and use the returned value to generate
+        a preview UI of the results. All tests/results can be exported/imported to formatted JSON:
+
+        `AuditTestCase` JSON:
+            {
+                "type": "test-case",
+                "name": <string>,
+                <optional> "description": <string>,
+                "test": <stringified JavaScript function>,
+            }
+
+        `AuditTestGroup` JSON:
+            {
+                "type": "test-group",
+                "name": <string>,
+                <optional> "description": <string>,
+                "tests": [...<AuditTestCase, AuditTestGroup>],
+            }
+
+        `AuditTestCaseResult` JSON:
+            {
+                "type": "test-case-result",
+                "name": <string>,
+                <optional> "description": <string>,
+                "level": <"pass", "warn", "fail", "error", "unsupported">,
+                "data": {
+                    "domNodes": [...<stringified CSS path>],
+                    "domAttributes": [...<string>],
+                    "errors": [...<string>],
+                },
+            }
+
+        `AuditTestGroupResult` JSON:
+            {
+                "type": "test-group-result",
+                "name": <string>,
+                <optional> "description": <string>,
+                "results": [...<AuditTestCaseResult, AuditTestGroupResult>],
+            }
+
+        More keys may be added in the future (especially for `AuditTestCaseResult.data`).
+
+        * UserInterface/Controllers/AuditManager.js:
+        (WI.AuditManager):
+        (WI.AuditManager.synthesizeError): Added.
+        (WI.AuditManager.prototype.get tests): Added.
+        (WI.AuditManager.prototype.get results): Added.
+        (WI.AuditManager.prototype.get runningState): Added.
+        (WI.AuditManager.prototype.start): Added.
+        (WI.AuditManager.prototype.stop): Added.
+        (WI.AuditManager.prototype.import): Added.
+        (WI.AuditManager.prototype.export): Added.
+        (WI.AuditManager.prototype._addTest): Added.
+        (WI.AuditManager.prototype._addResult): Added.
+        (WI.AuditManager.prototype.get testSuites): Deleted.
+        (WI.AuditManager.prototype.get reports): Deleted.
+        (WI.AuditManager.prototype.async runAuditTestByRepresentedObject): Deleted.
+        (WI.AuditManager.prototype.reportForId): Deleted.
+        (WI.AuditManager.prototype.removeAllReports): Deleted.
+        (WI.AuditManager.prototype.async _runTestCase): Deleted.
+
+        * UserInterface/Models/AuditTestBase.js: Added.
+        (WI.AuditTestBases):
+        (WI.AuditTestBases.prototype.get name):
+        (WI.AuditTestBases.prototype.get description):
+        (WI.AuditTestBases.prototype.get runningState):
+        (WI.AuditTestBases.prototype.get result):
+        (WI.AuditTestBases.prototype.async start):
+        (WI.AuditTestBases.prototype.stop):
+        (WI.AuditTestBases.prototype.clearResult):
+        (WI.AuditTestBases.prototype.saveIdentityToCookie):
+        (WI.AuditTestBases.prototype.toJSON):
+        (WI.AuditTestBases.prototype.async run):
+
+        * UserInterface/Models/AuditTestCase.js:
+        (WI.AuditTestCase):
+        (WI.AuditTestCase.fromPayload): Added.
+        (WI.AuditTestCase.prototype.toJSON): Added.
+        (WI.AuditTestCase.prototype.async run): Added.
+        (WI.AuditTestCase.prototype.async run.setLevel): Added.
+        (WI.AuditTestCase.prototype.async run.addError): Added.
+        (WI.AuditTestCase.prototype.async run.checkResultProperty.addErrorForValueType): Added.
+        (WI.AuditTestCase.prototype.async run.checkResultProperty): Added.
+        (WI.AuditTestCase.prototype.async run.async resultArrayForEach): Added.
+        (WI.AuditTestCase.prototype.get id): Deleted.
+        (WI.AuditTestCase.prototype.get name): Deleted.
+        (WI.AuditTestCase.prototype.get suite): Deleted.
+        (WI.AuditTestCase.prototype.get setup): Deleted.
+        (WI.AuditTestCase.prototype.get tearDown): Deleted.
+        (WI.AuditTestCase.prototype.get errorDetails): Deleted.
+
+        * UserInterface/Models/AuditTestGroup.js: Added.
+        (WI.AuditTestGroup):
+        (WI.AuditTestGroup.fromPayload):
+        (WI.AuditTestGroup.prototype.get tests):
+        (WI.AuditTestGroup.prototype.stop):
+        (WI.AuditTestGroup.prototype.clearResult):
+        (WI.AuditTestGroup.prototype.async run):
+        (WI.AuditTestGroup.prototype.toJSON):
+        (WI.AuditTestGroup.prototype._updateResult):
+        (WI.AuditTestGroup.prototype._handleTestCompleted):
+        (WI.AuditTestGroup.prototype._handleTestProgress):
+
+        * UserInterface/Models/AuditTestResultBase.js: Added.
+        (WI.AuditTestResultBase):
+        (WI.AuditTestResultBase.prototype.get name):
+        (WI.AuditTestResultBase.prototype.get description):
+        (WI.AuditTestResultBase.prototype.get result):
+        (WI.AuditTestResultBase.prototype.get didPass):
+        (WI.AuditTestResultBase.prototype.get didWarn):
+        (WI.AuditTestResultBase.prototype.get didFail):
+        (WI.AuditTestResultBase.prototype.get didError):
+        (WI.AuditTestResultBase.prototype.get unsupported):
+        (WI.AuditTestResultBase.prototype.saveIdentityToCookie):
+        (WI.AuditTestResultBase.prototype.toJSON):
+
+        * UserInterface/Models/AuditTestCaseResult.js: Added.
+        (WI.AuditTestCaseResult):
+        (WI.AuditTestCaseResult.fromPayload.checkArray):
+        (WI.AuditTestCaseResult.fromPayload):
+        (WI.AuditTestCaseResult.prototype.get level):
+        (WI.AuditTestCaseResult.prototype.get data):
+        (WI.AuditTestCaseResult.prototype.get didPass):
+        (WI.AuditTestCaseResult.prototype.get didWarn):
+        (WI.AuditTestCaseResult.prototype.get didFail):
+        (WI.AuditTestCaseResult.prototype.get didError):
+        (WI.AuditTestCaseResult.prototype.get unsupported):
+        (WI.AuditTestCaseResult.prototype.toJSON):
+
+        * UserInterface/Models/AuditTestGroupResult.js: Added.
+        (WI.AuditTestGroupResult):
+        (WI.AuditTestGroupResult.fromPayload):
+        (WI.AuditTestGroupResult.prototype.get results):
+        (WI.AuditTestGroupResult.prototype.get levelCounts):
+        (WI.AuditTestGroupResult.prototype.get didPass):
+        (WI.AuditTestGroupResult.prototype.get didWarn):
+        (WI.AuditTestGroupResult.prototype.get didFail):
+        (WI.AuditTestGroupResult.prototype.get didError):
+        (WI.AuditTestGroupResult.prototype.get unsupported):
+        (WI.AuditTestGroupResult.prototype.toJSON):
+
+        * UserInterface/Views/AuditTabContentView.js: Added.
+        (WI.AuditTabContentView):
+        (WI.AuditTabContentView.tabInfo):
+        (WI.AuditTabContentView.isTabAllowed):
+        (WI.AuditTabContentView.prototype.get type):
+        (WI.AuditTabContentView.prototype.get supportsSplitContentBrowser):
+        (WI.AuditTabContentView.prototype.canShowRepresentedObject):
+        (WI.AuditTabContentView.prototype.shown):
+        (WI.AuditTabContentView.prototype.hidden):
+        (WI.AuditTabContentView.prototype._handleSpace):
+
+        * UserInterface/Views/AuditNavigationSidebarPanel.js: Added.
+        (WI.AuditNavigationSidebarPanel):
+        (WI.AuditNavigationSidebarPanel.prototype.showDefaultContentView):
+        (WI.AuditNavigationSidebarPanel.prototype.initialLayout):
+        (WI.AuditNavigationSidebarPanel.prototype.closed):
+        (WI.AuditNavigationSidebarPanel.prototype._addTest):
+        (WI.AuditNavigationSidebarPanel.prototype._addResult):
+        (WI.AuditNavigationSidebarPanel.prototype._updateStartStopButtonNavigationItemState):
+        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestAdded):
+        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestCompleted):
+        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestScheduled):
+        (WI.AuditNavigationSidebarPanel.prototype._treeSelectionDidChange):
+        (WI.AuditNavigationSidebarPanel.prototype._handleStartStopButtonNavigationItemClicked):
+        (WI.AuditNavigationSidebarPanel.prototype._handleImportButtonNavigationItemClicked):
+        * UserInterface/Views/AuditNavigationSidebarPanel.css: Added.
+        (.sidebar > .panel.navigation.audit > .content):
+
+        * UserInterface/Views/AuditTreeElement.js: Added.
+        (WI.AuditTreeElement):
+        (WI.AuditTreeElement.prototype.get result):
+        (WI.AuditTreeElement.prototype.onattach):
+        (WI.AuditTreeElement.prototype.ondetach):
+        (WI.AuditTreeElement.prototype.onpopulate):
+        (WI.AuditTreeElement.prototype.populateContextMenu):
+        (WI.AuditTreeElement.prototype._start):
+        (WI.AuditTreeElement.prototype._updateLevel):
+        (WI.AuditTreeElement.prototype._showRunningSpinner):
+        (WI.AuditTreeElement.prototype._showRunningProgress):
+        (WI.AuditTreeElement.prototype._handleTestCaseCompleted):
+        (WI.AuditTreeElement.prototype._handleTestResultCleared):
+        (WI.AuditTreeElement.prototype._handleTestCaseScheduled):
+        (WI.AuditTreeElement.prototype._handleTestGroupCompleted):
+        (WI.AuditTreeElement.prototype._handleTestGroupProgress):
+        (WI.AuditTreeElement.prototype._handleTestGroupScheduled):
+        (WI.AuditTreeElement.prototype._handleStatusClick):
+        * UserInterface/Views/AuditTreeElement.css: Added.
+        (.tree-outline .item.audit > .status):
+        (.tree-outline .item.audit > .status > img):
+        (.tree-outline .item.audit:matches(.test-case, .test-group) > .status:hover > img):
+        (.tree-outline .item.audit > .status:not(:hover) > img.show-on-hover, .tree-outline .item.audit.test-group.expanded > .status:not(:hover)):
+        (.tree-outline .item.audit.test-group.expanded > .status:hover > :not(img), .tree-outline .item.audit.test-group-result.expanded > .status):
+        (.tree-outline .item.audit > .status > img.pass):
+        (.tree-outline .item.audit > .status > img.warn):
+        (.tree-outline .item.audit > .status > img.fail):
+        (.tree-outline .item.audit > .status > img.error):
+        (.tree-outline .item.audit > .status > img.unsupported):
+        (.audit.test-case .icon):
+        (.audit.test-group .icon):
+        (.audit.test-case-result .icon):
+        (.audit.test-group-result .icon):
+
+        * UserInterface/Views/AuditTestContentView.js: Added.
+        (WI.AuditTestContentView):
+        (WI.AuditTestContentView.prototype.get navigationItems):
+        (WI.AuditTestContentView.prototype.get headerView):
+        (WI.AuditTestContentView.prototype.get contentView):
+        (WI.AuditTestContentView.prototype.get supportsSave):
+        (WI.AuditTestContentView.prototype.get saveData):
+        (WI.AuditTestContentView.prototype.initialLayout):
+        (WI.AuditTestContentView.prototype.layout):
+        (WI.AuditTestContentView.prototype.shown):
+        (WI.AuditTestContentView.prototype.hidden):
+        (WI.AuditTestContentView.prototype.get placeholderElement):
+        (WI.AuditTestContentView.prototype.set placeholderElement):
+        (WI.AuditTestContentView.prototype.showRunningPlaceholder):
+        (WI.AuditTestContentView.prototype.showStoppingPlaceholder):
+        (WI.AuditTestContentView.prototype.showNoResultPlaceholder):
+        (WI.AuditTestContentView.prototype.showNoResultDataPlaceholder):
+        (WI.AuditTestContentView.prototype.showFilteredPlaceholder):
+        (WI.AuditTestContentView.prototype.hidePlaceholder):
+        (WI.AuditTestContentView.prototype.applyFilter):
+        (WI.AuditTestContentView.prototype.resetFilter):
+        (WI.AuditTestContentView.prototype._exportAudit):
+        (WI.AuditTestContentView.prototype._updateExportButtonNavigationItemState):
+        (WI.AuditTestContentView.prototype._showPlaceholder):
+        (WI.AuditTestContentView.prototype._handleExportButtonNavigationItemClicked):
+        (WI.AuditTestContentView.prototype._handleTestChanged):
+        * UserInterface/Views/AuditTestContentView.css: Added.
+        (.content-view-container > .content-view.audit-test):
+        (.content-view-container > .content-view.audit-test > header):
+        (.content-view-container > .content-view.audit-test > header h1):
+        (.content-view-container > .content-view.audit-test > header p):
+        (.content-view.audit-test):
+        (.content-view.audit-test h1):
+        (.content-view.audit-test > header):
+        (.content-view.audit-test > header p):
+        (.content-view.audit-test .audit-test.filtered, .content-view.audit-test .audit-test .message-text-view):
+        (.content-view.audit-test > section):
+        (.content-view.audit-test > section > .message-text-view):
+        (.content-view.audit-test.showing-placeholder):
+        (.content-view.audit-test.showing-placeholder > section):
+        (.content-view.audit-test.showing-placeholder > section > :not(.message-text-view)):
+        (@media (prefers-dark-interface) .content-view.audit-test):
+
+        * UserInterface/Views/AuditTestCaseContentView.js: Added.
+        (WI.AuditTestCaseContentView):
+        (WI.AuditTestCaseContentView.prototype.initialLayout):
+        (WI.AuditTestCaseContentView.prototype.layout):
+        (WI.AuditTestCaseContentView.prototype.showRunningPlaceholder):
+        * UserInterface/Views/AuditTestCaseContentView.css: Added.
+        (.content-view-container > .content-view.audit-test-case > header):
+        (.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view):first-child):
+        (.content-view.audit-test-case > header > h1):
+        (.content-view.audit-test-case > header > h1 > img):
+        (.content-view.audit-test-case > section > :not(.message-text-view)):
+        (.content-view.audit-test-case > section > :not(.message-text-view):last-child):
+        (.content-view.audit-test-case > section > :not(.message-text-view) + :not(.message-text-view)):
+        (.content-view.audit-test-case > section h1):
+        (.content-view.audit-test-case > section table):
+        (.content-view.audit-test-case > section table > tr + tr > td):
+        (.content-view.audit-test-case > section table > tr > td > :not(.tree-outline)):
+        (.content-view.audit-test-case > section table > tr > td:first-child):
+        (.content-view.audit-test-case > section > .dom-nodes > table > tr > td:first-child):
+        (.content-view.audit-test-case > section code):
+        (.content-view.audit-test-case > section mark):
+
+        * UserInterface/Views/AuditTestGroupContentView.js: Added.
+        (WI.AuditTestGroupContentView):
+        (WI.AuditTestGroupContentView.prototype.initialLayout):
+        (WI.AuditTestGroupContentView.prototype.layout):
+        (WI.AuditTestGroupContentView.prototype.shown):
+        (WI.AuditTestGroupContentView.prototype.hidden):
+        (WI.AuditTestGroupContentView.prototype.applyFilter):
+        (WI.AuditTestGroupContentView.prototype.resetFilter):
+        (WI.AuditTestGroupContentView.prototype.showRunningPlaceholder):
+        (WI.AuditTestGroupContentView.prototype._subobjects):
+        (WI.AuditTestGroupContentView.prototype._updateLevelScopeBar):
+        (WI.AuditTestGroupContentView.prototype._handleTestGroupCompleted):
+        (WI.AuditTestGroupContentView.prototype._handleTestGroupProgress):
+        (WI.AuditTestGroupContentView.prototype._handleTestGroupScheduled):
+        (WI.AuditTestGroupContentView.prototype._handleLevelScopeBarSelectionChanged):
+        * UserInterface/Views/AuditTestGroupContentView.css: Added.
+        (.content-view-container > .content-view.audit-test-group > header):
+        (.content-view.audit-test-group > header):
+        (.content-view.audit-test-group.no-matches + .audit-test-group > header):
+        (.content-view.audit-test-group > header, .content-view.audit-test-group:not(.filtered):last-child > header):
+        (.content-view.audit-test-group.contains-test-case > header):
+        (.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case):
+        (.content-view.audit-test-group.contains-test-case:not(.contains-test-group) > section, .content-view.audit-test-group.contains-test-case.contains-test-group > section > .audit-test-case):
+        (.content-view.audit-test-group > header > .information):
+        (.content-view.audit-test-group > header > .information > p):
+        (.content-view.audit-test-group > header > nav):
+        (.content-view.audit-test-group > header > nav:empty):
+        (.content-view.audit-test-group > header > nav:not(:empty):before):
+        (.content-view.audit-test-group > header > nav > .scope-bar > li):
+        (.content-view.audit-test-group > header > nav > .scope-bar > li:not(:hover, .selected)):
+        (.content-view.audit-test-group > header > nav > .scope-bar > li:last-child):
+        (.content-view.audit-test-group > header > nav > .scope-bar > li::before):
+        (.content-view.audit-test-group > header > nav > .scope-bar > li.pass::before):
+        (.content-view.audit-test-group > header > nav > .scope-bar > li.warn::before):
+        (.content-view.audit-test-group > header > nav > .scope-bar > li.fail::before):
+        (.content-view.audit-test-group > header > nav > .scope-bar > li.error::before):
+        (.content-view.audit-test-group > header > nav > .scope-bar > li.unsupported::before):
+        (.content-view.audit-test-group > header > .percentage-pass):
+        (.content-view.audit-test-group > header > .percentage-pass:not(:empty)::after):
+        (.content-view.audit-test-group > section > .audit-test-case:first-child, .content-view.audit-test-group > section > .audit-test-group + .audit-test-case, .content-view.audit-test-group > section > .audit-test-case + .audit-test-group):
+        (.content-view.audit-test-group > section > .audit-test-case:last-child):
+
+        * UserInterface/Views/ScopeBarItem.js:
+        (WI.ScopeBarItem):
+        (WI.ScopeBarItem.prototype.set selected):
+        * UserInterface/Views/MultipleScopeBarItem.js:
+        (WI.MultipleScopeBarItem.prototype.set selectedScopeBarItem):
+        Add an `independent` option that prevents selection changes from deselecting other
+        `WI.ScopeBarItem`s in the same `WI.ScopeBar` (`exclusive` takes precedence).
+
+        * UserInterface/Views/DOMTreeElement.js:
+        (WI.DOMTreeElement):
+        (WI.DOMTreeElement.prototype.highlightAttribute):
+        (WI.DOMTreeElement.prototype._buildAttributeDOM):
+        * UserInterface/Views/DOMTreeOutline.css:
+        (.tree-outline.dom li .highlight):
+
+        * UserInterface/Views/ToggleButtonNavigationItem.js:
+        (WI.ToggleButtonNavigationItem.prototype.set toggled):
+        Also change the `label` if the `ButtonStyle` has text.
+
+        * UserInterface/Base/Setting.js:
+        * UserInterface/Views/SettingsTabContentView.js:
+        (WI.SettingsTabContentView.prototype._createExperimentalSettingsView):
+
+        * UserInterface/Views/DividerNavigationItem.css:
+        (.navigation-bar .item.divider):
+
+        * UserInterface/Base/Utilities.js:
+        (Promise.chain): Added.
+
+        * UserInterface/Views/ContentView.js:
+        (WI.ContentView.createFromRepresentedObject):
+        (WI.ContentView.isViewable):
+
+        * UserInterface/Main.html:
+        * UserInterface/Base/Main.js:
+        (WI.loaded):
+        (WI.contentLoaded):
+
+        * UserInterface/Test.html:
+        * UserInterface/Base/Test.js:
+        (WI.loaded):
+
+        * UserInterface/Images/Audit.svg: Added.
+        * UserInterface/Images/AuditStart.svg: Added.
+        * UserInterface/Images/AuditStop.svg: Added.
+        * UserInterface/Images/AuditTestCase.svg: Added.
+        * UserInterface/Images/AuditTestCaseResult.svg: Added.
+        * UserInterface/Images/AuditTestError.svg: Added.
+        * UserInterface/Images/AuditTestFail.svg: Added.
+        * UserInterface/Images/AuditTestGroup.svg: Added.
+        * UserInterface/Images/AuditTestGroupResult.svg: Added.
+        * UserInterface/Images/AuditTestNoResult.svg: Added.
+        * UserInterface/Images/AuditTestPass.svg: Added.
+        * UserInterface/Images/AuditTestUnsupported.svg: Added.
+        * UserInterface/Images/AuditTestWarn.svg: Added.
+
+        * Localizations/en.lproj/localizedStrings.js:
+
+2018-10-30  Devin Rousso  <drousso@apple.com>
+
</ins><span class="cx">         Web Inspector: provide options to WI.cssPath for more verbosity
</span><span class="cx">         https://bugs.webkit.org/show_bug.cgi?id=190987
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebInspectorUILocalizationsenlprojlocalizedStringsjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js   2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js      2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -22,12 +22,17 @@
</span><span class="cx"> localizedStrings["%.2fms"] = "%.2fms";
</span><span class="cx"> localizedStrings["%.2fs"] = "%.2fs";
</span><span class="cx"> localizedStrings["%.3fms"] = "%.3fms";
</span><ins>+localizedStrings["%d Error"] = "%d Error";
</ins><span class="cx"> localizedStrings["%d Errors"] = "%d Errors";
</span><span class="cx"> localizedStrings["%d Errors, %d Warnings"] = "%d Errors, %d Warnings";
</span><ins>+localizedStrings["%d Fail"] = "%d Fail";
</ins><span class="cx"> localizedStrings["%d Frame"] = "%d Frame";
</span><span class="cx"> localizedStrings["%d Frames"] = "%d Frames";
</span><span class="cx"> localizedStrings["%d More\u2026"] = "%d More\u2026";
</span><ins>+localizedStrings["%d Pass"] = "%d Pass";
</ins><span class="cx"> localizedStrings["%d Threads"] = "%d Threads";
</span><ins>+localizedStrings["%d Unsupported"] = "%d Unsupported";
+localizedStrings["%d Warn"] = "%d Warn";
</ins><span class="cx"> localizedStrings["%d Warnings"] = "%d Warnings";
</span><span class="cx"> localizedStrings["%d \xd7 %d pixels"] = "%d \xd7 %d pixels";
</span><span class="cx"> localizedStrings["%d \xd7 %d pixels (Natural: %d \xd7 %d pixels)"] = "%d \xd7 %d pixels (Natural: %d \xd7 %d pixels)";
</span><span class="lines">@@ -43,6 +48,7 @@
</span><span class="cx"> localizedStrings["%s Event Dispatched"] = "%s Event Dispatched";
</span><span class="cx"> localizedStrings["%s Fired"] = "%s Fired";
</span><span class="cx"> localizedStrings["%s Prototype"] = "%s Prototype";
</span><ins>+localizedStrings["%s Result"] = "%s Result";
</ins><span class="cx"> localizedStrings["%s \u2013 %s"] = "%s \u2013 %s";
</span><span class="cx"> localizedStrings["%s \u2014 %s"] = "%s \u2014 %s";
</span><span class="cx"> localizedStrings["%s cannot be modified"] = "%s cannot be modified";
</span><span class="lines">@@ -88,6 +94,9 @@
</span><span class="cx"> localizedStrings["All Requests"] = "All Requests";
</span><span class="cx"> localizedStrings["All Resources"] = "All Resources";
</span><span class="cx"> localizedStrings["All Storage"] = "All Storage";
</span><ins>+localizedStrings["All items in “%s“ must be error objects"] = "All items in “%s“ must be error objects";
+localizedStrings["All items in “%s“ must be non-empty strings"] = "All items in “%s“ must be non-empty strings";
+localizedStrings["All items in “%s“ must be valid DOM nodes"] = "All items in “%s“ must be valid DOM nodes";
</ins><span class="cx"> localizedStrings["An error occurred trying to load the resource."] = "An error occurred trying to load the resource.";
</span><span class="cx"> localizedStrings["An error occurred trying to read the “%s” table."] = "An error occurred trying to read the “%s” table.";
</span><span class="cx"> localizedStrings["An unexpected error %s occurred."] = "An unexpected error %s occurred.";
</span><span class="lines">@@ -113,6 +122,10 @@
</span><span class="cx"> localizedStrings["Attribute"] = "Attribute";
</span><span class="cx"> localizedStrings["Attribute Modified"] = "Attribute Modified";
</span><span class="cx"> localizedStrings["Attributes"] = "Attributes";
</span><ins>+localizedStrings["Audit"] = "Audit";
+localizedStrings["Audit test error: %s"] = "Audit test error: %s";
+localizedStrings["Audit:"] = "Audit:";
+localizedStrings["Audits"] = "Audits";
</ins><span class="cx"> localizedStrings["Author Stylesheet"] = "Author Stylesheet";
</span><span class="cx"> localizedStrings["Auto Increment"] = "Auto Increment";
</span><span class="cx"> localizedStrings["Automatically continue after evaluating"] = "Automatically continue after evaluating";
</span><span class="lines">@@ -244,6 +257,7 @@
</span><span class="cx"> localizedStrings["DOM Content Loaded \u2014 %s"] = "DOM Content Loaded \u2014 %s";
</span><span class="cx"> localizedStrings["DOM Event"] = "DOM Event";
</span><span class="cx"> localizedStrings["DOM Events"] = "DOM Events";
</span><ins>+localizedStrings["DOM Nodes:"] = "DOM Nodes:";
</ins><span class="cx"> localizedStrings["Damping"] = "Damping";
</span><span class="cx"> localizedStrings["Data"] = "Data";
</span><span class="cx"> localizedStrings["Data returned from the database is too large."] = "Data returned from the database is too large.";
</span><span class="lines">@@ -329,6 +343,7 @@
</span><span class="cx"> localizedStrings["Element may overlap another compositing element"] = "Element may overlap another compositing element";
</span><span class="cx"> localizedStrings["Element overlaps other compositing element"] = "Element overlaps other compositing element";
</span><span class="cx"> localizedStrings["Elements"] = "Elements";
</span><ins>+localizedStrings["Enable Audit Tab"] = "Enable Audit Tab";
</ins><span class="cx"> localizedStrings["Enable Breakpoint"] = "Enable Breakpoint";
</span><span class="cx"> localizedStrings["Enable Breakpoints"] = "Enable Breakpoints";
</span><span class="cx"> localizedStrings["Enable Event Listener"] = "Enable Event Listener";
</span><span class="lines">@@ -346,6 +361,7 @@
</span><span class="cx"> localizedStrings["Error"] = "Error";
</span><span class="cx"> localizedStrings["Error: "] = "Error: ";
</span><span class="cx"> localizedStrings["Errors"] = "Errors";
</span><ins>+localizedStrings["Errors:"] = "Errors:";
</ins><span class="cx"> localizedStrings["Eval Code"] = "Eval Code";
</span><span class="cx"> localizedStrings["Evaluate JavaScript"] = "Evaluate JavaScript";
</span><span class="cx"> localizedStrings["Event"] = "Event";
</span><span class="lines">@@ -362,6 +378,8 @@
</span><span class="cx"> localizedStrings["Expires"] = "Expires";
</span><span class="cx"> localizedStrings["Export"] = "Export";
</span><span class="cx"> localizedStrings["Export HAR"] = "Export HAR";
</span><ins>+localizedStrings["Export Result"] = "Export Result";
+localizedStrings["Export Test"] = "Export Test";
</ins><span class="cx"> localizedStrings["Export recording (%s)"] = "Export recording (%s)";
</span><span class="cx"> localizedStrings["Expression"] = "Expression";
</span><span class="cx"> localizedStrings["Extension Scripts"] = "Extension Scripts";
</span><span class="lines">@@ -453,6 +471,7 @@
</span><span class="cx"> localizedStrings["Immediate Pause Requested"] = "Immediate Pause Requested";
</span><span class="cx"> localizedStrings["Import"] = "Import";
</span><span class="cx"> localizedStrings["Import recording from file"] = "Import recording from file";
</span><ins>+localizedStrings["Imported"] = "Imported";
</ins><span class="cx"> localizedStrings["Imported Recordings"] = "Imported Recordings";
</span><span class="cx"> localizedStrings["Incomplete"] = "Incomplete";
</span><span class="cx"> localizedStrings["Indent width:"] = "Indent width:";
</span><span class="lines">@@ -537,6 +556,7 @@
</span><span class="cx"> localizedStrings["Message"] = "Message";
</span><span class="cx"> localizedStrings["Method"] = "Method";
</span><span class="cx"> localizedStrings["Microtask Dispatched"] = "Microtask Dispatched";
</span><ins>+localizedStrings["Missing result level"] = "Missing result level";
</ins><span class="cx"> localizedStrings["Mixed"] = "Mixed";
</span><span class="cx"> localizedStrings["Module Code"] = "Module Code";
</span><span class="cx"> localizedStrings["Multi-Entry"] = "Multi-Entry";
</span><span class="lines">@@ -565,9 +585,11 @@
</span><span class="cx"> localizedStrings["No Query Parameters"] = "No Query Parameters";
</span><span class="cx"> localizedStrings["No Request Headers"] = "No Request Headers";
</span><span class="cx"> localizedStrings["No Response Headers"] = "No Response Headers";
</span><ins>+localizedStrings["No Result"] = "No Result";
</ins><span class="cx"> localizedStrings["No Results Found"] = "No Results Found";
</span><span class="cx"> localizedStrings["No Search Results"] = "No Search Results";
</span><span class="cx"> localizedStrings["No Watch Expressions"] = "No Watch Expressions";
</span><ins>+localizedStrings["No audit selected"] = "No audit selected";
</ins><span class="cx"> localizedStrings["No matching ARIA role"] = "No matching ARIA role";
</span><span class="cx"> localizedStrings["No preview available"] = "No preview available";
</span><span class="cx"> localizedStrings["No request cookies."] = "No request cookies.";
</span><span class="lines">@@ -626,7 +648,9 @@
</span><span class="cx"> localizedStrings["Port"] = "Port";
</span><span class="cx"> localizedStrings["Prefer indent using:"] = "Prefer indent using:";
</span><span class="cx"> localizedStrings["Preserve Log"] = "Preserve Log";
</span><ins>+localizedStrings["Press %s to import a test or result file"] = "Press %s to import a test or result file";
</ins><span class="cx"> localizedStrings["Press %s to load a recording from file."] = "Press %s to load a recording from file.";
</span><ins>+localizedStrings["Press %s to start running the audit"] = "Press %s to start running the audit";
</ins><span class="cx"> localizedStrings["Pressed"] = "Pressed";
</span><span class="cx"> localizedStrings["Pretty print"] = "Pretty print";
</span><span class="cx"> localizedStrings["Preview"] = "Preview";
</span><span class="lines">@@ -699,11 +723,14 @@
</span><span class="cx"> localizedStrings["Response:"] = "Response:";
</span><span class="cx"> localizedStrings["Restart (%s)"] = "Restart (%s)";
</span><span class="cx"> localizedStrings["Restart animation"] = "Restart animation";
</span><ins>+localizedStrings["Results"] = "Results";
</ins><span class="cx"> localizedStrings["Resume Processing"] = "Resume Processing";
</span><span class="cx"> localizedStrings["Resume Thread"] = "Resume Thread";
</span><span class="cx"> localizedStrings["Retained Size"] = "Retained Size";
</span><ins>+localizedStrings["Return string must be one of %s"] = "Return string must be one of %s";
</ins><span class="cx"> localizedStrings["Return type for anonymous function"] = "Return type for anonymous function";
</span><span class="cx"> localizedStrings["Return type for function: %s"] = "Return type for function: %s";
</span><ins>+localizedStrings["Return value is not an object, string, or boolean"] = "Return value is not an object, string, or boolean";
</ins><span class="cx"> localizedStrings["Reveal Breakpoint"] = "Reveal Breakpoint";
</span><span class="cx"> localizedStrings["Reveal in DOM Tree"] = "Reveal in DOM Tree";
</span><span class="cx"> localizedStrings["Reveal in Debugger Tab"] = "Reveal in Debugger Tab";
</span><span class="lines">@@ -713,6 +740,8 @@
</span><span class="cx"> localizedStrings["Reveal in Original Resource"] = "Reveal in Original Resource";
</span><span class="cx"> localizedStrings["Reveal in Resources Tab"] = "Reveal in Resources Tab";
</span><span class="cx"> localizedStrings["Role"] = "Role";
</span><ins>+localizedStrings["Run %d"] = "Run %d";
+localizedStrings["Running the “%s“ audit"] = "Running the “%s“ audit";
</ins><span class="cx"> localizedStrings["Same-Site"] = "Same-Site";
</span><span class="cx"> localizedStrings["Samples"] = "Samples";
</span><span class="cx"> localizedStrings["Save File"] = "Save File";
</span><span class="lines">@@ -790,6 +819,7 @@
</span><span class="cx"> localizedStrings["Show type information"] = "Show type information";
</span><span class="cx"> localizedStrings["Show warnings logged to the Console"] = "Show warnings logged to the Console";
</span><span class="cx"> localizedStrings["Show:"] = "Show:";
</span><ins>+localizedStrings["Showing:"] = "Showing:";
</ins><span class="cx"> localizedStrings["Size"] = "Size";
</span><span class="cx"> localizedStrings["Size of current object plus all objects it keeps alive"] = "Size of current object plus all objects it keeps alive";
</span><span class="cx"> localizedStrings["Sizes"] = "Sizes";
</span><span class="lines">@@ -809,6 +839,7 @@
</span><span class="cx"> localizedStrings["Specificity: No value for selected element"] = "Specificity: No value for selected element";
</span><span class="cx"> localizedStrings["Spelling"] = "Spelling";
</span><span class="cx"> localizedStrings["Stalled"] = "Stalled";
</span><ins>+localizedStrings["Start"] = "Start";
</ins><span class="cx"> localizedStrings["Start Time"] = "Start Time";
</span><span class="cx"> localizedStrings["Start element selection (%s)"] = "Start element selection (%s)";
</span><span class="cx"> localizedStrings["Start recording (%s)\nCreate new recording (%s)"] = "Start recording (%s)\nCreate new recording (%s)";
</span><span class="lines">@@ -821,11 +852,13 @@
</span><span class="cx"> localizedStrings["Step out (%s or %s)"] = "Step out (%s or %s)";
</span><span class="cx"> localizedStrings["Step over (%s or %s)"] = "Step over (%s or %s)";
</span><span class="cx"> localizedStrings["Stiffness"] = "Stiffness";
</span><ins>+localizedStrings["Stop"] = "Stop";
</ins><span class="cx"> localizedStrings["Stop Recording"] = "Stop Recording";
</span><span class="cx"> localizedStrings["Stop element selection (%s)"] = "Stop element selection (%s)";
</span><span class="cx"> localizedStrings["Stop recording"] = "Stop recording";
</span><span class="cx"> localizedStrings["Stop recording (%s)"] = "Stop recording (%s)";
</span><span class="cx"> localizedStrings["Stop recording canvas actions"] = "Stop recording canvas actions";
</span><ins>+localizedStrings["Stopping the “%s“ audit"] = "Stopping the “%s“ audit";
</ins><span class="cx"> localizedStrings["Storage"] = "Storage";
</span><span class="cx"> localizedStrings["Style Attribute"] = "Style Attribute";
</span><span class="cx"> localizedStrings["Style rule"] = "Style rule";
</span><span class="lines">@@ -849,6 +882,11 @@
</span><span class="cx"> localizedStrings["Text Frame"] = "Text Frame";
</span><span class="cx"> localizedStrings["Text Node"] = "Text Node";
</span><span class="cx"> localizedStrings["The page's content has changed"] = "The page's content has changed";
</span><ins>+localizedStrings["The “%s“ audit failed"] = "The “%s“ audit failed";
+localizedStrings["The “%s“ audit is unsupported"] = "The “%s“ audit is unsupported";
+localizedStrings["The “%s“ audit passed"] = "The “%s“ audit passed";
+localizedStrings["The “%s“ audit threw an error"] = "The “%s“ audit threw an error";
+localizedStrings["The “%s“ audit warned"] = "The “%s“ audit warned";
</ins><span class="cx"> localizedStrings["The “%s”\ntable is empty."] = "The “%s”\ntable is empty.";
</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="lines">@@ -939,6 +977,7 @@
</span><span class="cx"> localizedStrings["computed"] = "computed";
</span><span class="cx"> localizedStrings["default"] = "default";
</span><span class="cx"> localizedStrings["for changes to take effect"] = "for changes to take effect";
</span><ins>+localizedStrings["invalid JSON."] = "invalid JSON.";
</ins><span class="cx"> localizedStrings["key"] = "key";
</span><span class="cx"> localizedStrings["line "] = "line ";
</span><span class="cx"> localizedStrings["originally %s"] = "originally %s";
</span><span class="lines">@@ -951,6 +990,8 @@
</span><span class="cx"> localizedStrings["unsupported version."] = "unsupported version.";
</span><span class="cx"> localizedStrings["value"] = "value";
</span><span class="cx"> localizedStrings["“%s“ Event Fired"] = "“%s“ Event Fired";
</span><ins>+localizedStrings["“%s“ must be a %s"] = "“%s“ must be a %s";
+localizedStrings["“%s“ must be an %s"] = "“%s“ must be an %s";
</ins><span class="cx"> localizedStrings["“%s” Profile Recorded"] = "“%s” Profile Recorded";
</span><span class="cx"> localizedStrings["“%s” is invalid."] = "“%s” is invalid.";
</span><span class="cx"> localizedStrings["“%s” threw an error."] = "“%s” threw an error.";
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceBaseMainjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Main.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Base/Main.js   2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Main.js      2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -127,6 +127,7 @@
</span><span class="cx">     this.workerManager = new WI.WorkerManager;
</span><span class="cx">     this.domDebuggerManager = new WI.DOMDebuggerManager;
</span><span class="cx">     this.canvasManager = new WI.CanvasManager;
</span><ins>+    this.auditManager = new WI.AuditManager;
</ins><span class="cx"> 
</span><span class="cx">     // Enable the Console Agent after creating the singleton managers.
</span><span class="cx">     ConsoleAgent.enable();
</span><span class="lines">@@ -446,6 +447,7 @@
</span><span class="cx">         WI.StorageTabContentView,
</span><span class="cx">         WI.CanvasTabContentView,
</span><span class="cx">         WI.LayersTabContentView,
</span><ins>+        WI.AuditTabContentView,
</ins><span class="cx">         WI.ConsoleTabContentView,
</span><span class="cx">         WI.SearchTabContentView,
</span><span class="cx">         WI.NewTabContentView,
</span><span class="lines">@@ -1114,6 +1116,10 @@
</span><span class="cx">         representedObject instanceof WI.IndexedDatabase || representedObject instanceof WI.IndexedDatabaseObjectStoreIndex)
</span><span class="cx">         return WI.StorageTabContentView;
</span><span class="cx"> 
</span><ins>+    if (representedObject instanceof WI.AuditTestCase || representedObject instanceof WI.AuditTestGroup
+        || representedObject instanceof WI.AuditTestCaseResult || representedObject instanceof WI.AuditTestGroupResult)
+        return WI.AuditTabContentView;
+
</ins><span class="cx">     if (representedObject instanceof WI.CanvasCollection)
</span><span class="cx">         return WI.CanvasTabContentView;
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceBaseSettingjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Setting.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Base/Setting.js        2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Setting.js   2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -125,6 +125,7 @@
</span><span class="cx">     experimentalEnableMultiplePropertiesSelection: new WI.Setting("experimental-enable-multiple-properties-selection", false),
</span><span class="cx">     experimentalEnableLayersTab: new WI.Setting("experimental-enable-layers-tab", false),
</span><span class="cx">     experimentalEnableNewTabBar: new WI.Setting("experimental-enable-new-tab-bar", false),
</span><ins>+    experimentalEnableAuditTab: new WI.Setting("experimental-enable-audit-tab", false),
</ins><span class="cx"> 
</span><span class="cx">     // DebugUI
</span><span class="cx">     autoLogProtocolMessages: new WI.Setting("auto-collect-protocol-messages", false),
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceBaseUtilitiesjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js      2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js 2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1372,6 +1372,17 @@
</span><span class="cx">     }
</span><span class="cx"> });
</span><span class="cx"> 
</span><ins>+Object.defineProperty(Promise, "chain",
+{
+    async value(callbacks, initialValue)
+    {
+        let results = [];
+        for (let i = 0; i < callbacks.length; ++i)
+            results.push(await callbacks[i](results.lastValue || initialValue || null, i));
+        return results;
+    }
+});
+
</ins><span class="cx"> Object.defineProperty(Promise, "delay",
</span><span class="cx"> {
</span><span class="cx">     value(delay)
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceControllersAuditManagerjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js    2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js       2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -29,111 +29,148 @@
</span><span class="cx">     {
</span><span class="cx">         super();
</span><span class="cx"> 
</span><del>-        this._testSuiteConstructors = [];
-        this._reports = new Map;
</del><ins>+        this._tests = [];
+        this._results = [];
</ins><span class="cx"> 
</span><del>-        // Transforming all the constructors into AuditTestSuite instances.
-        this._testSuites = this._testSuiteConstructors.map(suite => {
-            let newTestSuite = new suite;
</del><ins>+        this._runningState = WI.AuditManager.RunningState.Inactive;
+        this._runningTests = [];
+    }
</ins><span class="cx"> 
</span><del>-            if (!(newTestSuite instanceof WI.AuditTestSuite))
-                throw new Error("Audit test suites must be of instance WI.AuditTestSuite.");
</del><ins>+    static synthesizeError(message)
+    {
+        let consoleMessage = new WI.ConsoleMessage(WI.mainTarget, WI.ConsoleMessage.MessageSource.Other, WI.ConsoleMessage.MessageLevel.Error, WI.UIString("Audit test error: %s").format(message));
+        consoleMessage.shouldRevealConsole = true;
</ins><span class="cx"> 
</span><del>-            return newTestSuite;
-        });
</del><ins>+        WI.consoleLogViewController.appendConsoleMessage(consoleMessage);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     // Public
</span><span class="cx"> 
</span><del>-    get testSuites() { return this._testSuites.slice(); }
-    get reports() { return [...this._reports.values()]; }
</del><ins>+    get tests() { return this._tests; }
+    get results() { return this._results; }
+    get runningState() { return this._runningState; }
</ins><span class="cx"> 
</span><del>-    async runAuditTestByRepresentedObject(representedObject)
</del><ins>+    async start(tests)
</ins><span class="cx">     {
</span><del>-        let auditReport = new WI.AuditReport(representedObject);
</del><ins>+        console.assert(this._runningState === WI.AuditManager.RunningState.Inactive);
+        if (this._runningState !== WI.AuditManager.RunningState.Inactive)
+            return;
</ins><span class="cx"> 
</span><del>-        if (representedObject instanceof WI.AuditTestCase) {
-            let auditResult = await this._runTestCase(representedObject);
-            auditReport.addResult(auditResult);
-        } else if (representedObject instanceof WI.AuditTestSuite) {
-            let testCases = representedObject.testCases;
-            // Start reducing from testCases[0].
-            let result = testCases.slice(1).reduce((chain, testCase, index) => {
-                if (testCase.setup) {
-                    let setup = testCase.setup.call(testCase, testCase.suite);
-                    if (testCase.setup[Symbol.toStringTag] === "AsyncFunction")
-                        return setup;
-                    else
-                        return new Promise(setup);
-                }
</del><ins>+        if (tests && tests.length)
+            tests = tests.filter((test) => typeof test === "object" && test instanceof WI.AuditTestBase);
+        else
+            tests = this._tests;
</ins><span class="cx"> 
</span><del>-                chain = chain.then((auditResult) => {
-                    auditReport.addResult(auditResult);
-                    return this._runTestCase(testCase);
-                });
</del><ins>+        if (!tests.length)
+            return;
</ins><span class="cx"> 
</span><del>-                if (testCase.tearDown) {
-                    let tearDown = testCase.tearDown.call(testCase, testCase.suite);
-                    if (testCase.tearDown[Symbol.toStringTag] === "AsyncFunction")
-                        return tearDown;
-                    else
-                        return new Promise(tearDown);
-                }
-                return chain;
-            }, this._runTestCase(testCases[0]));
</del><ins>+        this._runningState = WI.AuditManager.RunningState.Active;
+        this._runningTests = tests;
+        for (let test of this._runningTests)
+            test.clearResult();
</ins><span class="cx"> 
</span><del>-            let lastAuditResult = await result;
-            auditReport.addResult(lastAuditResult);
</del><ins>+        this.dispatchEventToListeners(WI.AuditManager.Event.TestScheduled);
</ins><span class="cx"> 
</span><del>-            // Make AuditReport read-only after all the AuditResults have been received.
-            auditReport.close();
-        }
</del><ins>+        await Promise.chain(this._runningTests.map((test) => () => this._runningState === WI.AuditManager.RunningState.Active ? test.start() : null));
</ins><span class="cx"> 
</span><del>-        this._reports.set(representedObject.id, auditReport);
-        this.dispatchEventToListeners(WI.AuditManager.Event.NewReportAdded, {auditReport});
</del><ins>+        let result = this._runningTests.map((test) => test.result).filter((result) => !!result);
</ins><span class="cx"> 
</span><del>-        return auditReport;
</del><ins>+        this._runningState = WI.AuditManager.RunningState.Inactive;
+        this._runningTests = [];
+
+        this._addResult(result);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    addTestSuite(auditTestSuiteConstructor)
</del><ins>+    stop()
</ins><span class="cx">     {
</span><del>-        if (this._testSuiteConstructors.indexOf(auditTestSuiteConstructor) >= 0)
-            throw new Error(`class ${auditTestSuiteConstructor.name} already exists.`);
</del><ins>+        console.assert(this._runningState === WI.AuditManager.RunningState.Active);
+        if (this._runningState !== WI.AuditManager.RunningState.Active)
+            return;
</ins><span class="cx"> 
</span><del>-        let auditTestSuite = new auditTestSuiteConstructor;
-        this._testSuiteConstructors.push(auditTestSuiteConstructor);
-        this._testSuites.push(auditTestSuite);
</del><ins>+        for (let test of this._runningTests)
+            test.stop();
+
+        this._runningState = WI.AuditManager.RunningState.Stopping;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    reportForId(reportId)
</del><ins>+    import()
</ins><span class="cx">     {
</span><del>-        return this._reports.get(reportId);
</del><ins>+        WI.loadDataFromFile((data, filename) => {
+            if (!data)
+                return;
+
+            let payload = null;
+            try {
+                payload = JSON.parse(data);
+            } catch (e) {
+                WI.AuditManager.synthesizeError(e);
+                return;
+            }
+
+            let object = WI.AuditTestGroup.fromPayload(payload) || WI.AuditTestCase.fromPayload(payload);
+            if (!object) {
+                object = WI.AuditTestGroupResult.fromPayload(payload) || WI.AuditTestCaseResult.fromPayload(payload);
+                if (!object) {
+                    WI.AuditManager.synthesizeError(WI.UIString("invalid JSON."));
+                    return;
+                }
+            }
+
+            if (object instanceof WI.AuditTestBase)
+                this._addTest(object);
+            else if (object instanceof WI.AuditTestResultBase)
+                this._addResult(object);
+        });
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    removeAllReports()
</del><ins>+    export(object)
</ins><span class="cx">     {
</span><del>-        this._reports.clear();
</del><ins>+        console.assert(object instanceof WI.AuditTestCase || object instanceof WI.AuditTestGroup || object instanceof WI.AuditTestCaseResult || object instanceof WI.AuditTestGroupResult, object);
+
+        let filename = object.name;
+        if (object instanceof WI.AuditTestResultBase)
+            filename = WI.UIString("%s Result").format(filename);
+
+        let url = "web-inspector:///" + encodeURI(filename) + ".json";
+
+        WI.saveDataToFile({
+            url,
+            content: JSON.stringify(object),
+            forceSaveAs: true,
+        });
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     // Private
</span><span class="cx"> 
</span><del>-    async _runTestCase(testCase)
</del><ins>+    _addTest(test)
</ins><span class="cx">     {
</span><del>-        let didRaiseException = false;
-        let result;
-        this.dispatchEventToListeners(WI.AuditManager.Event.TestStarted, {test: testCase});
-        try {
-            result = await testCase.test.call(testCase, testCase.suite);
-        } catch (resultData) {
-            result = resultData;
-            didRaiseException = true;
-        }
-        this.dispatchEventToListeners(WI.AuditManager.Event.TestEnded, {test: testCase});
-        return new WI.AuditResult(testCase, {result}, didRaiseException);
</del><ins>+        this._tests.push(test);
+
+        this.dispatchEventToListeners(WI.AuditManager.Event.TestAdded, {test});
</ins><span class="cx">     }
</span><ins>+
+    _addResult(result)
+    {
+        if (!result || (Array.isArray(result) && !result.length))
+            return;
+
+        this._results.push(result);
+
+        this.dispatchEventToListeners(WI.AuditManager.Event.TestCompleted, {
+            result,
+            index: this._results.length - 1,
+        });
+    }
</ins><span class="cx"> };
</span><span class="cx"> 
</span><ins>+WI.AuditManager.RunningState = {
+    Inactive: "inactive",
+    Active: "active",
+    Stopping: "stopping",
+};
+
</ins><span class="cx"> WI.AuditManager.Event = {
</span><del>-    TestStarted: Symbol("test-started"),
-    TestEnded: Symbol("test-ended")
</del><ins>+    TestAdded: "audit-manager-test-added",
+    TestCompleted: "audit-manager-test-completed",
+    TestScheduled: "audit-manager-test-scheduled",
</ins><span class="cx"> };
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceImagesAuditsvg"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Images/Audit.svg (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Images/Audit.svg                               (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/Audit.svg  2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,11 @@
</span><ins>+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <rect fill="none" stroke="currentColor" x="0.5" y="0.5" width="15" height="15" rx="2"/>
+    <path fill="none" stroke-linecap="round" stroke="currentColor" d="M 3.5 5.5 L 12.5 5.5"/>
+    <path fill="none" stroke-linecap="round" stroke="currentColor" d="M 12.5 5.5 L 9.5 2.5"/>
+    <path fill="none" stroke-linecap="round" stroke="currentColor" d="M 12.5 5.5 L 9.5 8.5"/>
+    <path fill="none" stroke-linecap="round" stroke="currentColor" d="M 3.5 10.5 L 12.5 10.5"/>
+    <path fill="none" stroke-linecap="round" stroke="currentColor" d="M 3.5 10.5 L 6.5 7.5"/>
+    <path fill="none" stroke-linecap="round" stroke="currentColor" d="M 3.5 10.5 L 6.5 13.5"/>
+</svg>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceImagesAuditStartsvg"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Images/AuditStart.svg (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Images/AuditStart.svg                          (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/AuditStart.svg     2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,5 @@
</span><ins>+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 12 12">
+    <path fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M 11 6 L 2 1 v 10 z"/>
+</svg>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceImagesAuditStopsvg"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Images/AuditStop.svg (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Images/AuditStop.svg                           (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/AuditStop.svg      2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,5 @@
</span><ins>+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 12 12">
+    <path fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M 2 2 h 8 v 8 H 2 z"/>
+</svg>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceImagesAuditTestCasesvg"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestCase.svg (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestCase.svg                               (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestCase.svg  2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,8 @@
</span><ins>+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <path fill="rgb(148, 183, 219)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 Z"/>
+    <path fill="rgb(106, 136, 170)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 M 13 2 C 13.550781 2 14 2.449219 14 3 L 14 13 C 14 13.550781 13.550781 14 13 14 L 3 14 C 2.449219 14 2 13.550781 2 13 L 2 3 C 2 2.449219 2.449219 2 3 2 L 13 2"/>
+    <path fill="white" d="M 9.1 12.4 c -1.8 0 -2.6 -0.7 -2.6 -2.3 V 7.4 H 4.6 V 5.1 h 1.9 V 3.6 h 2.6 v 1.5 h 2.2 v 2.3 H 9.2 v 2.7 h 0.6 l 0.6 -0.1 v 2.2 l -0.4 0.1 c -0.2 0 -0.5 0.1 -0.9 0.1 z"/>
+    <path fill="rgb(113, 146, 184)" d="M 8.7 4.1 v 1.5 h 2.2 v 1.3 H 8.7 v 3 c 0 0.6 0.3 0.8 0.9 0.8 h 0.3 v 1.2 c -0.1 0 -0.4 0.1 -0.7 0.1 -1.6 0 -2.1 -0.5 -2.1 -1.8 V 6.9 h -2 V 5.6 H 7 V 4.1 h 1.7 m 1 -1 H 6.1 v 1.5 h -2 v 3.3 H 6 v 2.2 c 0 1.9 1.1 2.8 3.1 2.8 0.3 0 0.7 0 0.9 -0.1 l 0.8 -0.2 V 9.4 l -1.1 0.2 h -0.1 V 7.9 h 2.2 V 4.6 H 9.7 V 3.1 sz"/>
+</svg>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceImagesAuditTestCaseResultsvg"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestCaseResult.svg (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestCaseResult.svg                         (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestCaseResult.svg    2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,8 @@
</span><ins>+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <path fill="rgb(203, 203, 203)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 Z"/>
+    <path fill="rgb(153, 153, 153)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 M 13 2 C 13.550781 2 14 2.449219 14 3 L 14 13 C 14 13.550781 13.550781 14 13 14 L 3 14 C 2.449219 14 2 13.550781 2 13 L 2 3 C 2 2.449219 2.449219 2 3 2 L 13 2"/>
+    <path fill="white" d="M 9.1 12.4 c -1.8 0 -2.6 -0.7 -2.6 -2.3 V 7.4 H 4.6 V 5.1 h 1.9 V 3.6 h 2.6 v 1.5 h 2.2 v 2.3 H 9.2 v 2.7 h 0.6 l 0.6 -0.1 v 2.2 l -0.4 0.1 c -0.2 0 -0.5 0.1 -0.9 0.1 z"/>
+    <path fill="rgb(166, 166, 166)" d="M 8.7 4.1 v 1.5 h 2.2 v 1.3 H 8.7 v 3 c 0 0.6 0.3 0.8 0.9 0.8 h 0.3 v 1.2 c -0.1 0 -0.4 0.1 -0.7 0.1 -1.6 0 -2.1 -0.5 -2.1 -1.8 V 6.9 h -2 V 5.6 H 7 V 4.1 h 1.7 m 1 -1 H 6.1 v 1.5 h -2 v 3.3 H 6 v 2.2 c 0 1.9 1.1 2.8 3.1 2.8 0.3 0 0.7 0 0.9 -0.1 l 0.8 -0.2 V 9.4 l -1.1 0.2 h -0.1 V 7.9 h 2.2 V 4.6 H 9.7 V 3.1 sz"/>
+</svg>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceImagesAuditTestErrorsvg"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestError.svg (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestError.svg                              (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestError.svg 2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,9 @@
</span><ins>+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <path fill="rgb(216, 82, 92)" stroke="rgb(219, 25, 34)" stroke-width="0.5" stroke-miterlimit="10" d="M 10.7 1.5 H 5.3 L 1.5 5.3 v 5.4 l 3.8 3.8 h 5.4 l 3.8 -3.8 V 5.3 z"/>
+    <path fill="white" d="M 8 9.165 c -0.588 0 -1.165 -0.665 -1.165 -1.341 V 4.176 c 0 -0.802 0.468 -1.341 1.165 -1.341 s 1.165 0.539 1.165 1.341 v 3.648 c 0 0.677 -0.577 1.341 -1.165 1.341 z"/>
+    <path fill="rgb(219, 25, 34)" d="M 8 3 c 0.6 0 1 0.47 1 1.177 v 3.647 C 9 8.412 8.5 9 8 9 s -1 -0.588 -1 -1.176 V 4.177 C 7 3.47 7.4 3 8 3 m 0 -0.33 c -0.783 0 -1.33.62 -1.33 1.506 v 3.648 c 0 0.774 0.646 1.506 1.33 1.506 s 1.33 -0.732 1.33 -1.506 V 4.177 C 9.33 3.29 8.783 2.67 8 2.67 z"/>
+    <circle fill="white" cx="8" cy="11.5" r="1.415"/>
+    <path fill="rgb(219, 25, 34)" d="M 8 10.25 a 1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0 -2.5 m 0 -0.33 c -0.871 0 -1.58 0.709 -1.58 1.58 s 0.709 1.58 1.58 1.58 1.58 -0.709 1.58 -1.58 S 8.871 9.92 8 9.92 z"/>
+</svg>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceImagesAuditTestFailsvg"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestFail.svg (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestFail.svg                               (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestFail.svg  2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,7 @@
</span><ins>+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <path fill="rgb(216, 82, 92)" stroke="rgb(219, 25, 34)" stroke-width="0.5" stroke-miterlimit="10" d="M 7.3 14.7 l -6 -6 c -0.4 -0.4 -0.4 -1.1 0 -1.5 l 6 -6 c 0.4 -0.4 1.1 -0.4 1.5 0 l 6 6 c 0.4 0.4 0.4 1.1 0 1.5 l -6 6 c -0.5 0.4 -1.1 0.4 -1.5 0 z"/>
+    <path fill="white" d="M 10.191 11.206 a 0.162 0.162 0 0 1 -0.116 -0.049 L 8 9.082 l -2.075 2.075 a 0.164 0.164 0 0 1 -0.234 0 l -0.848 -0.848 a 0.164 0.164 0 0 1 0 -0.233 L 6.918 8 4.843 5.925 a 0.165 0.165 0 0 1 0 -0.234 l 0.848 -0.848 a 0.165 0.165 0 0 1 0.234 0 L 8 6.918 l 2.075 -2.075 a 0.163 0.163 0 0 1 0.233 0 l 0.848 0.848 a 0.165 0.165 0 0 1 0 0.234 L 9.082 8 l 2.075 2.076 a 0.165 0.165 0 0 1 0 0.233l -0.85 0.848 a 0.161 0.161 0 0 1 -0.116 0.05 z"/>
+    <path fill="rgb(219, 25, 34)" d="M 10.192 4.96 l 0.848 0.848 L 8.848 8 l 2.192 2.192 -0.848 0.849 L 8 8.848 5.808 11.04 l -0.849 -0.848 L 7.151 8 4.96 5.808 l 0.849 -0.849 L 8 7.152 l 2.192 -2.192 m 0 -0.33 a 0.329 0.329 0 0 0 -0.233 0.096 l -1.96 1.959 -1.958 -1.959 a 0.329 0.329 0 0 0 -0.467 0 l -0.848 0.849 a 0.33 0.33 0 0 0 0 0.466 L 6.685 8 4.726 9.959 a 0.33 0.33 0 0 0 0 0.466 l 0.849 0.849 a 0.33 0.33 0 0 0 0.466 0 L 8 9.315 l 1.959 1.959 a 0.33 0.33 0 0 0 0.466 0 l 0.849 -0.849 a 0.33 0.33 0 0 0 0 -0.466 L 9.315 8 l 1.959 -1.959 a 0.33 0.33 0 0 0 0 -0.466 l -0.849 -0.849 a 0.33 0.33 0 0 0 -0.233 -0.096 z"/>
+</svg>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceImagesAuditTestGroupsvg"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestGroup.svg (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestGroup.svg                              (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestGroup.svg 2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,8 @@
</span><ins>+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <path fill="rgb(148, 183, 219)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 Z"/>
+    <path fill="rgb(106, 136, 170)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 M 13 2 C 13.550781 2 14 2.449219 14 3 L 14 13 C 14 13.550781 13.550781 14 13 14 L 3 14 C 2.449219 14 2 13.550781 2 13 L 2 3 C 2 2.449219 2.449219 2 3 2 L 13 2"/>
+    <path fill="rgb(113, 146, 184)" d="M 6.980469 12.742188 C 6.429688 12.742188 5.980469 12.292969 5.980469 11.742188 C 5.980469 11.742188 5.980469 7.34375 5.980469 5.882812 C 5.140625 5.882812 4.136719 5.882812 4.136719 5.882812 C 3.582031 5.882812 3.136719 5.4375 3.136719 4.882812 L 3.136719 3.792969 C 3.136719 3.242188 3.582031 2.792969 4.136719 2.792969 L 11.488281 2.792969 C 12.042969 2.792969 12.488281 3.242188 12.488281 3.792969 L 12.488281 4.882812 C 12.488281 5.4375 12.042969 5.882812 11.488281 5.882812 C 11.488281 5.882812 10.484375 5.882812 9.640625 5.882812 C 9.640625 7.34375 9.640625 11.742188 9.640625 11.742188 C 9.640625 12.292969 9.195312 12.742188 8.640625 12.742188 L 6.980469 12.742188"/>
+    <path fill="white" d="M 6.980469 11.742188 L 6.980469 4.882812 L 4.136719 4.882812 L 4.136719 3.792969 L 11.488281 3.792969 L 11.488281 4.882812 L 8.640625 4.882812 L 8.640625 11.742188 Z"/>
+</svg>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceImagesAuditTestGroupResultsvg"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestGroupResult.svg (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestGroupResult.svg                                (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestGroupResult.svg   2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,8 @@
</span><ins>+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <path fill="rgb(203, 203, 203)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 Z"/>
+    <path fill="rgb(153, 153, 153)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 M 13 2 C 13.550781 2 14 2.449219 14 3 L 14 13 C 14 13.550781 13.550781 14 13 14 L 3 14 C 2.449219 14 2 13.550781 2 13 L 2 3 C 2 2.449219 2.449219 2 3 2 L 13 2"/>
+    <path fill="rgb(166, 166, 166)" d="M 6.980469 12.742188 C 6.429688 12.742188 5.980469 12.292969 5.980469 11.742188 C 5.980469 11.742188 5.980469 7.34375 5.980469 5.882812 C 5.140625 5.882812 4.136719 5.882812 4.136719 5.882812 C 3.582031 5.882812 3.136719 5.4375 3.136719 4.882812 L 3.136719 3.792969 C 3.136719 3.242188 3.582031 2.792969 4.136719 2.792969 L 11.488281 2.792969 C 12.042969 2.792969 12.488281 3.242188 12.488281 3.792969 L 12.488281 4.882812 C 12.488281 5.4375 12.042969 5.882812 11.488281 5.882812 C 11.488281 5.882812 10.484375 5.882812 9.640625 5.882812 C 9.640625 7.34375 9.640625 11.742188 9.640625 11.742188 C 9.640625 12.292969 9.195312 12.742188 8.640625 12.742188 L 6.980469 12.742188"/>
+    <path fill="white" d="M 6.980469 11.742188 L 6.980469 4.882812 L 4.136719 4.882812 L 4.136719 3.792969 L 11.488281 3.792969 L 11.488281 4.882812 L 8.640625 4.882812 L 8.640625 11.742188 Z"/>
+</svg>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceImagesAuditTestNoResultsvg"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestNoResult.svg (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestNoResult.svg                           (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestNoResult.svg      2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,6 @@
</span><ins>+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <circle fill="rgb(202, 202, 202)" stroke="rgb(153, 153, 153)" stroke-width="0.5" stroke-miterlimit="10" cx="8" cy="8" r="6.5"/>
+    <path fill="white" stroke="rgb(153, 153, 153)" stroke-width="0.33" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M 9.25 11.563 a 1.25 1.25 0 1 1 -2.5 0 1.25 1.25 0 0 1 2.5 0 z m -0.973 -8.25 c 1.653 0 2.973 0.832 2.973 2.486 0 1.159 -0.642 1.622 -1.23 1.992 -0.708 0.444 -1 0.632 -1 1.271 v 0.25 H 6.951 l -0.018 -0.131 c -0.136 -1.015 0.137 -1.722 0.946 -2.205 0.559 -0.332 0.933 -0.563 0.933 -1.052 0 -0.47 -0.339 -0.695 -0.69 -0.695 -0.422 0 -0.683 0.338 -0.695 0.783 h -2.17 c -0.12 -1.854 1.35 -2.7 3.02 -2.7 z"/>
+</svg>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceImagesAuditTestPasssvg"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestPass.svg (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestPass.svg                               (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestPass.svg  2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,7 @@
</span><ins>+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <path fill="rgb(181, 201, 116)" stroke="rgb(148, 172, 85)" stroke-width="0.5" stroke-miterlimit="10" d="M 7.3 14.7 l -6 -6 c -0.4 -0.4 -0.4 -1.1 0 -1.5 l 6 -6 c 0.4 -0.4 1.1 -0.4 1.5 0 l 6 6 c 0.4 0.4 0.4 1.1 0 1.5 l -6 6 c -0.5 0.4 -1.1 0.4 -1.5 0 z"/>
+    <path fill="white" d="M 6.9 11.465 a 0.164 0.164 0 0 1 -0.127 -0.06 l -2.4 -2.9 a 0.165 0.165 0 0 1 -0.002 -0.208 l 0.8 -1 a 0.165 0.165 0 0 1 0.127 -0.062 H 5.3 c 0.049 0 0.095 0.021 0.126 0.058 l 1.472 1.749 3.175 -3.847 a 0.165 0.165 0 0 1 0.127 -0.06 h 0.001 c 0.05 0 0.097 0.023 0.128 0.062 l 0.8 1 a 0.165 0.165 0 0 1 -0.001 0.207 l -4.1 5 a 0.164 0.164 0 0 1 -0.128 0.06 z"/>
+    <path fill="rgb(148, 172, 85)" d="M 10.2 5.3 l 0.8 1 -4.1 5 -2.4 -2.9 0.8 -1 1.6 1.9 3.3 -4 m 0 -0.33 a 0.33 0.33 0 0 0 -0.255 0.12 L 6.897 8.785 5.552 7.187 A 0.33 0.33 0 0 0 5.3 7.07 h -0.004 a 0.33 0.33 0 0 0 -0.254 0.124 l -0.8 1 a 0.33 0.33 0 0 0 0.004 0.416 l 2.4 2.9 a 0.33 0.33 0 0 0 0.51 -0.001 l 4.1 -5 a 0.33 0.33 0 0 0 0.002 -0.415 l -0.8 -1 a 0.33 0.33 0 0 0 -0.256 -0.124 H 10.2 z"/>
+</svg>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceImagesAuditTestUnsupportedsvg"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestUnsupported.svg (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestUnsupported.svg                                (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestUnsupported.svg   2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,7 @@
</span><ins>+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <path fill="rgb(202, 202, 202)" stroke="rgb(153, 153, 153)" stroke-width="0.5" stroke-miterlimit="10" d="M 7.3 14.7 l -6 -6 c -0.4 -0.4 -0.4 -1.1 0 -1.5 l 6 -6 c 0.4 -0.4 1.1 -0.4 1.5 0 l 6 6 c 0.4 0.4 0.4 1.1 0 1.5 l -6 6 c -0.5 0.4 -1.1 0.4 -1.5 0 z"/>
+    <path fill="white" d="M 4.5 9.2 c -0.4 0 -0.7 -0.3 -0.7 -0.7 v -1 c 0 -0.4 0.3 -0.7 0.7 -0.7 h 7 c 0.4 0 0.7 0.3 0.7 0.7 v 1 c 0 0.4 -0.3 0.7 -0.7 0.7 h -7 z"/>
+    <path fill="rgb(153, 153, 153)" d="M 11.5 7 c 0.3 0 0.5 0.2 0.5 0.5 v 1 c 0 0.3 -0.2 0.5 -0.5 0.5 h -7 c -0.3 0 -0.5 -0.2 -0.5 -0.5 v -1 c 0 -0.3 0.2 -0.5 0.5 -0.5 h 7 m 0 -0.3 h -7 c -0.5 0 -0.8 0.3 -0.8 0.8 v 1 c 0 0.5 0.3 0.8 0.8 0.8 h 7 c 0.5 0 0.8 -0.4 0.8 -0.8 v -1 c 0 -0.5 -0.3 -0.8 -0.8 -0.8 z"/>
+</svg>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceImagesAuditTestWarnsvg"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestWarn.svg (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestWarn.svg                               (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Images/AuditTestWarn.svg  2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,9 @@
</span><ins>+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2018 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <path fill="rgb(243, 222, 156)" stroke="rgb(201, 181, 118)" stroke-width="0.5" stroke-miterlimit="10" d="M 7.3 14.7 l -6 -6 c -0.4 -0.4 -0.4 -1.1 0 -1.5 l 6 -6 c 0.4 -0.4 1.1 -0.4 1.5 0 l 6 6 c 0.4 0.4 0.4 1.1 0 1.5 l -6 6 c -0.5 0.4 -1.1 0.4 -1.5 0 z"/>
+    <circle fill="white" cx="8" cy="11.5" r="1.415"/>
+    <path fill="rgb(201, 181, 118)" d="M 8 10.25 a 1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0 -2.5 m 0 -0.33 c -0.871 0 -1.58 0.709 -1.58 1.58 s 0.709 1.58 1.58 1.58 1.58 -0.709 1.58 -1.58 S 8.871 9.92 8 9.92 z"/>
+    <path fill="white" d="M 8 9.165 A 1.166 1.166 0 0 1 6.835 8 V 4 a 1.166 1.166 0 0 1 2.33 0 v 4 c 0 0.643 -0.522 1.165 -1.165 1.165 z"/>
+    <path fill="rgb(201, 181, 118)" d="M 8 3 c 0.55 0 1 0.45 1 1 v 4 c 0 0.55 -0.45 1 -1 1 s -1 -0.45 -1 -1 V 4 c 0 -0.55 0.45 -1 1 -1 m 0 -0.33 c -0.733 0 -1.33 0.597 -1.33 1.33 v 4 c 0 0.733 0.597 1.33 1.33 1.33 S 9.33 8.733 9.33 8 V 4 c 0 -0.733 -0.597 -1.33 -1.33 -1.33 z"/>
+</svg>
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceMainhtml"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Main.html (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Main.html      2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Main.html 2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -31,6 +31,11 @@
</span><span class="cx">     <link rel="stylesheet" href="External/CodeMirror/codemirror.css">
</span><span class="cx"> 
</span><span class="cx">     <link rel="stylesheet" href="Views/ApplicationCacheFrameContentView.css">
</span><ins>+    <link rel="stylesheet" href="Views/AuditNavigationSidebarPanel.css">
+    <link rel="stylesheet" href="Views/AuditTestCaseContentView.css">
+    <link rel="stylesheet" href="Views/AuditTestContentView.css">
+    <link rel="stylesheet" href="Views/AuditTestGroupContentView.css">
+    <link rel="stylesheet" href="Views/AuditTreeElement.css">
</ins><span class="cx">     <link rel="stylesheet" href="Views/BezierEditor.css">
</span><span class="cx">     <link rel="stylesheet" href="Views/BoxModelDetailsSectionRow.css">
</span><span class="cx">     <link rel="stylesheet" href="Views/BreakpointActionView.css">
</span><span class="lines">@@ -440,10 +445,12 @@
</span><span class="cx">     <script src="Models/WrappedPromise.js"></script>
</span><span class="cx">     <script src="Models/XHRBreakpoint.js"></script>
</span><span class="cx"> 
</span><del>-    <script src="Models/AuditReport.js"></script>
-    <script src="Models/AuditResult.js"></script>
</del><ins>+    <script src="Models/AuditTestBase.js"></script>
</ins><span class="cx">     <script src="Models/AuditTestCase.js"></script>
</span><del>-    <script src="Models/AuditTestSuite.js"></script>
</del><ins>+    <script src="Models/AuditTestGroup.js"></script>
+    <script src="Models/AuditTestResultBase.js"></script>
+    <script src="Models/AuditTestCaseResult.js"></script>
+    <script src="Models/AuditTestGroupResult.js"></script>
</ins><span class="cx"> 
</span><span class="cx">     <script src="Proxies/FormatterWorkerProxy.js"></script>
</span><span class="cx">     <script src="Proxies/HeapSnapshotDiffProxy.js"></script>
</span><span class="lines">@@ -516,6 +523,7 @@
</span><span class="cx">     <script src="Views/ComputedStyleDetailsSidebarPanel.js"></script>
</span><span class="cx">     <script src="Views/StyleDetailsPanel.js"></script>
</span><span class="cx"> 
</span><ins>+    <script src="Views/AuditTabContentView.js"></script>
</ins><span class="cx">     <script src="Views/CanvasTabContentView.js"></script>
</span><span class="cx">     <script src="Views/ConsoleTabContentView.js"></script>
</span><span class="cx">     <script src="Views/DebuggerTabContentView.js"></script>
</span><span class="lines">@@ -546,6 +554,11 @@
</span><span class="cx">     <script src="Views/ApplicationCacheFrameContentView.js"></script>
</span><span class="cx">     <script src="Views/ApplicationCacheFrameTreeElement.js"></script>
</span><span class="cx">     <script src="Views/ApplicationCacheManifestTreeElement.js"></script>
</span><ins>+    <script src="Views/AuditNavigationSidebarPanel.js"></script>
+    <script src="Views/AuditTestContentView.js"></script>
+    <script src="Views/AuditTestCaseContentView.js"></script>
+    <script src="Views/AuditTestGroupContentView.js"></script>
+    <script src="Views/AuditTreeElement.js"></script>
</ins><span class="cx">     <script src="Views/BezierEditor.js"></script>
</span><span class="cx">     <script src="Views/BoxModelDetailsSectionRow.js"></script>
</span><span class="cx">     <script src="Views/BreakpointActionView.js"></script>
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditReportjs"></a>
<div class="delfile"><h4>Deleted: trunk/Source/WebInspectorUI/UserInterface/Models/AuditReport.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditReport.js  2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditReport.js     2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,62 +0,0 @@
</span><del>-/*
- * Copyright (C) 2018 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-WI.AuditReport = class AuditReport
-{
-    constructor(representedTest)
-    {
-        console.assert(representedTest instanceof WI.AuditTestCase || representedTest instanceof WI.AuditTestSuite);
-
-        this._results = [];
-        this._isWritable = true;
-        this._representedTestCases = (representedTest instanceof WI.AuditTestCase) ? [representedTest] : [...representedTest.testCases];
-        this._representedTestSuite = (representedTest instanceof WI.AuditTestSuite) ? representedTest : null;
-    }
-
-    // Public
-
-    get representedTestCases() { return this._representedTestCases.slice(); }
-    get representedTestSuite() { return this._representedTestSuite; }
-    get resultsData() { return this._results.slice(); }
-    get isWritable() { return this._isWritable; }
-
-    get failedCount() {
-        return this._results.slice().filter(result => result.failed).length;
-    }
-
-    addResult(auditResult)
-    {
-        if (!this._isWritable)
-            return;
-
-        console.assert(auditResult instanceof WI.AuditResult);
-        this._results.push(auditResult);
-    }
-
-    close()
-    {
-        this._isWritable = false;
-    }
-};
</del></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditResultjs"></a>
<div class="delfile"><h4>Deleted: trunk/Source/WebInspectorUI/UserInterface/Models/AuditResult.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditResult.js  2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditResult.js     2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,52 +0,0 @@
</span><del>-/*
- * Copyright (C) 2018 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-WI.AuditResult = class AuditResult
-{
-    constructor(testInstance, testResult, failed)
-    {
-        this._testResult = testResult;
-        this._failed = failed || false;
-        this._testName = testInstance.name;
-        this._errorDetails = testInstance.errorDetails;
-        this._logLevel = this._errorDetails.logLevel || WI.AuditResult.LogLevel.Passed;
-        this._errorTitle = this._errorDetails.title;
-        this._hint = this._errorDetails.hint;
-        this._documentation = this._errorDetails.documentation;
-    }
-
-    // Public
-
-    get testResult() { return this._testResult; }
-    get name() { return this._testName; }
-    get logLevel() { return this._logLevel; }
-    get failed() { return this._failed; }
-};
-
-WI.AuditResult.LogLevel = {
-    Error: "error",
-    Warning: "warning",
-    Passed: "passed"
-};
</del></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestBasejs"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js                                (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js   2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,124 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.AuditTestBase = class AuditTestBase extends WI.Object
+{
+    constructor(name, {description} = {})
+    {
+        console.assert(typeof name === "string");
+        console.assert(!description || typeof description === "string");
+
+        super();
+
+        this._name = name;
+        this._description = description || null;
+
+        this._runningState = WI.AuditManager.RunningState.Inactive;
+        this._result = null;
+    }
+
+    // Public
+
+    get name() { return this._name; }
+    get description() { return this._description; }
+    get runningState() { return this._runningState; }
+    get result() { return this._result; }
+
+    async start()
+    {
+        // Called from WI.AuditManager.
+
+        console.assert(WI.auditManager.runningState === WI.AuditManager.RunningState.Active);
+
+        console.assert(this._runningState === WI.AuditManager.RunningState.Inactive);
+        if (this._runningState !== WI.AuditManager.RunningState.Inactive)
+            return;
+
+        this._runningState = WI.AuditManager.RunningState.Active;
+        this.dispatchEventToListeners(WI.AuditTestBase.Event.Scheduled);
+
+        await this.run();
+
+        this._runningState = WI.AuditManager.RunningState.Inactive;
+        this.dispatchEventToListeners(WI.AuditTestBase.Event.Completed);
+    }
+
+    stop()
+    {
+        // Called from WI.AuditManager.
+
+        console.assert(this._runningState !== WI.AuditManager.RunningState.Inactive);
+
+        if (this._runningState !== WI.AuditManager.RunningState.Active)
+            return;
+
+        this._runningState = WI.AuditManager.RunningState.Stopping;
+        this.dispatchEventToListeners(WI.AuditTestBase.Event.Stopping);
+    }
+
+    clearResult(options = {})
+    {
+        if (!this._result)
+            return false;
+
+        this._result = null;
+
+        if (!options.suppressResultClearedEvent)
+            this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultCleared);
+
+        return true;
+    }
+
+    saveIdentityToCookie(cookie)
+    {
+        cookie["audit-" + this.constructor.TypeIdentifier + "-name"] = this._name;
+    }
+
+    toJSON()
+    {
+        let json = {
+            type: this.constructor.TypeIdentifier,
+            name: this._name,
+        };
+        if (this._description)
+            json.description = this._description;
+        return json;
+    }
+
+    // Protected
+
+    async run()
+    {
+        throw WI.NotImplementedError.subclassMustOverride();
+    }
+};
+
+WI.AuditTestBase.Event = {
+    Completed: "audit-test-base-completed",
+    Progress: "audit-test-base-progress",
+    ResultCleared: "audit-test-base-result-cleared",
+    Scheduled: "audit-test-base-scheduled",
+    Stopping: "audit-test-base-stopping",
+};
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestCasejs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js        2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js   2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -23,40 +23,217 @@
</span><span class="cx">  * THE POSSIBILITY OF SUCH DAMAGE.
</span><span class="cx">  */
</span><span class="cx"> 
</span><del>-WI.AuditTestCase = class AuditTestCase extends WI.Object
</del><ins>+WI.AuditTestCase = class AuditTestCase extends WI.AuditTestBase
</ins><span class="cx"> {
</span><del>-    constructor(suite, name, test, setup, tearDown, errorDetails = {})
</del><ins>+    constructor(name, test, {description} = {})
</ins><span class="cx">     {
</span><del>-        console.assert(suite instanceof WI.AuditTestSuite);
-        console.assert(typeof name === "string");
</del><ins>+        console.assert(typeof test === "string");
</ins><span class="cx"> 
</span><del>-        if (setup)
-            console.assert(setup instanceof Function);
</del><ins>+        super(name, {description});
</ins><span class="cx"> 
</span><del>-        if (tearDown)
-            console.assert(tearDown instanceof Function);
</del><ins>+        this._test = test;
+    }
</ins><span class="cx"> 
</span><del>-        if (test[Symbol.toStringTag] !== "AsyncFunction")
-            throw new Error("Test functions must be async functions.");
</del><ins>+    // Static
</ins><span class="cx"> 
</span><del>-        super();
-        this._id = Symbol(name);
</del><ins>+    static fromPayload(payload)
+    {
+        if (typeof payload !== "object" || payload === null)
+            return null;
</ins><span class="cx"> 
</span><del>-        this._suite = suite;
-        this._name = name;
-        this._test = test;
-        this._setup = setup;
-        this._tearDown = tearDown;
-        this._errorDetails = errorDetails;
</del><ins>+        let {type, name, test, description} = payload;
+
+        if (type !== WI.AuditTestCase.TypeIdentifier)
+            return null;
+
+        if (typeof name !== "string")
+            return null;
+
+        if (typeof test !== "string")
+            return null;
+
+        let options = {};
+        if (typeof description === "string")
+            options.description = description;
+
+        return new WI.AuditTestCase(name, test, options);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     // Public
</span><span class="cx"> 
</span><del>-    get id() { return this._id; }
-    get name() { return this._name; }
-    get suite() { return this._suite; }
</del><span class="cx">     get test() { return this._test; }
</span><del>-    get setup() { return this._setup; }
-    get tearDown() { return this._tearDown; }
-    get errorDetails() { return this._errorDetails; }
</del><ins>+
+    toJSON()
+    {
+        let json = super.toJSON();
+        json.test = this._test;
+        return json;
+    }
+
+    // Protected
+
+    async run()
+    {
+        const levelStrings = Object.values(WI.AuditTestCaseResult.Level);
+        let level = null;
+        let data = {};
+
+        function setLevel(newLevel) {
+            let newLevelIndex = levelStrings.indexOf(newLevel);
+            if (newLevelIndex < 0) {
+                addError(WI.UIString("Return string must be one of %s").format(JSON.stringify(levelStrings)));
+                return;
+            }
+
+            if (newLevelIndex <= levelStrings.indexOf(level))
+                return;
+
+            level = newLevel;
+        }
+
+        function addError(value) {
+            setLevel(WI.AuditTestCaseResult.Level.Error);
+
+            if (!data.errors)
+                data.errors = [];
+
+            data.errors.push(value);
+        }
+
+        try {
+            let {result, wasThrown} = await RuntimeAgent.evaluate.invoke({
+                expression: `(function() { "use strict"; return eval(${this._test})(); })()`,
+                objectGroup: "audit",
+                doNotPauseOnExceptionsAndMuteConsole: true,
+            });
+            let remoteObject = WI.RemoteObject.fromPayload(result, WI.mainTarget);
+
+            if (wasThrown || (remoteObject.type === "object" && remoteObject.subtype === "error"))
+                addError(remoteObject.description);
+            else if (remoteObject.type === "boolean")
+                setLevel(remoteObject.value ? WI.AuditTestCaseResult.Level.Pass : WI.AuditTestCaseResult.Level.Fail);
+            else if (remoteObject.type === "string")
+                setLevel(remoteObject.value.trim().toLowerCase());
+            else if (remoteObject.type === "object" && !remoteObject.subtype) {
+                const options = {
+                    ownProperties: true,
+                };
+
+                let properties = await new Promise((resolve, reject) => remoteObject.getPropertyDescriptorsAsObject(resolve, options));
+
+                function checkResultProperty(key, type, subtype) {
+                    if (!(key in properties))
+                        return null;
+
+                    let property = properties[key].value;
+                    if (!property)
+                        return null;
+
+                    function addErrorForValueType(valueType) {
+                        let value = null;
+                        if (valueType === "object" || valueType === "array")
+                            value = WI.UIString("“%s“ must be an %s");
+                        else
+                            value = WI.UIString("“%s“ must be a %s");
+                        addError(value.format(key, valueType));
+                    }
+
+                    if (property.subtype !== subtype) {
+                        addErrorForValueType(subtype);
+                        return null;
+                    }
+
+                    if (property.type !== type) {
+                        addErrorForValueType(type);
+                        return null;
+                    }
+
+                    if (type === "boolean" || type === "string")
+                        return property.value;
+
+                    return property;
+                }
+
+                async function resultArrayForEach(key, callback) {
+                    let array = checkResultProperty(key, "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]);
+                    }
+                }
+
+                let levelString = checkResultProperty("level", "string");
+                if (levelString)
+                    setLevel(levelString.trim().toLowerCase());
+
+                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);
+
+                await resultArrayForEach("domNodes", async (item) => {
+                    if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "node") {
+                        addError(WI.UIString("All items in “%s“ must be valid DOM nodes").format(WI.unlocalizedString("domNodes")));
+                        return;
+                    }
+
+                    let domNodeId = await new Promise((resolve, reject) => item.value.pushNodeToFrontend(resolve));
+                    let domNode = WI.domManager.nodeForId(domNodeId);
+                    if (!domNode)
+                        return;
+
+                    if (!data.domNodes)
+                        data.domNodes = [];
+                    data.domNodes.push(domNode);
+                });
+
+                await resultArrayForEach("domAttributes", (item) => {
+                    if (!item || !item.value || item.value.type !== "string" || !item.value.value.length) {
+                        addError(WI.UIString("All items in “%s“ must be non-empty strings").format(WI.unlocalizedString("domAttributes")));
+                        return;
+                    }
+
+                    if (!data.domAttributes)
+                        data.domAttributes = [];
+                    data.domAttributes.push(item.value.value);
+                });
+
+                await resultArrayForEach("errors", (item) => {
+                    if (!item || !item.value || item.value.type !== "object" || item.value.subtype !== "error") {
+                        addError(WI.UIString("All items in “%s“ must be error objects").format(WI.unlocalizedString("errors")));
+                        return;
+                    }
+
+                    addError(item.value.description);
+                });
+            } else
+                addError(WI.UIString("Return value is not an object, string, or boolean"));
+        } catch (error) {
+            addError(error.message);
+        }
+
+        if (!level)
+            addError(WI.UIString("Missing result level"));
+
+        let options = {
+            description: this.description,
+        };
+        if (!isEmptyObject(data))
+            options.data = data;
+        this._result = new WI.AuditTestCaseResult(this.name, level, options);
+    }
</ins><span class="cx"> };
</span><ins>+
+WI.AuditTestCase.TypeIdentifier = "test-case";
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestCaseResultjs"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCaseResult.js (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCaseResult.js                          (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCaseResult.js     2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,145 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.AuditTestCaseResult = class AuditTestCaseResult extends WI.AuditTestResultBase
+{
+    constructor(name, level, {data, description} = {})
+    {
+        console.assert(Object.values(WI.AuditTestCaseResult.Level).includes(level));
+        console.assert(!data || typeof data === "object");
+
+        super(name, {description});
+
+        this._level = level;
+        this._data = data || {};
+    }
+
+    // Static
+
+    static fromPayload(payload)
+    {
+        if (typeof payload !== "object" || payload === null)
+            return null;
+
+        let {type, name, description, level, data} = payload;
+
+        if (type !== WI.AuditTestCaseResult.TypeIdentifier)
+            return null;
+
+        if (typeof name !== "string")
+            return null;
+
+        if (!Object.values(WI.AuditTestCaseResult.Level).includes(level))
+            return null;
+
+        if (typeof data !== "object" || data === null)
+            data = {};
+
+        function checkArray(key) {
+            if (!data[key])
+                return;
+
+            if (!Array.isArray(data[key]))
+                data[key] = [];
+
+            data[key] = data[key].filter((item) => typeof item === "string");
+        }
+        checkArray("domNodes");
+        checkArray("domAttributes");
+        checkArray("errors");
+
+        let options = {};
+        if (typeof description === "string")
+            options.description = description;
+        if (!isEmptyObject(data))
+            options.data = data;
+        return new WI.AuditTestCaseResult(name, level, options);
+    }
+
+    // Public
+
+    get level() { return this._level; }
+    get data() { return this._data; }
+
+    get result()
+    {
+        return this;
+    }
+
+    get didPass()
+    {
+        return this._level === WI.AuditTestCaseResult.Level.Pass;
+    }
+
+    get didWarn()
+    {
+        return this._level === WI.AuditTestCaseResult.Level.Warn;
+    }
+
+    get didFail()
+    {
+        return this._level === WI.AuditTestCaseResult.Level.Fail;
+    }
+
+    get didError()
+    {
+        return this._level === WI.AuditTestCaseResult.Level.Error;
+    }
+
+    get unsupported()
+    {
+        return this._level === WI.AuditTestCaseResult.Level.Unsupported;
+    }
+
+    toJSON()
+    {
+        let json = super.toJSON();
+        json.level = this._level;
+
+        let data = {};
+        if (this._data.domNodes && this._data.domNodes.length) {
+            data.domNodes = this._data.domNodes.map((domNode) => domNode instanceof WI.DOMNode ? WI.cssPath(domNode, {full: true}) : domNode);
+            if (this._data.domAttributes && this._data.domAttributes.length)
+                data.domAttributes = this._data.domAttributes;
+        }
+        if (this._data.errors && this._data.errors.length)
+            data.errors = this._data.errors;
+        if (!isEmptyObject(data))
+            json.data = data;
+
+        return json;
+    }
+};
+
+WI.AuditTestCaseResult.TypeIdentifier = "test-case-result";
+
+// Keep this ordered by precedence.
+WI.AuditTestCaseResult.Level = {
+    Pass: "pass",
+    Warn: "warn",
+    Fail: "fail",
+    Error: "error",
+    Unsupported: "unsupported",
+};
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestGroupjs"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js                               (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js  2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,169 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.AuditTestGroup = class AuditTestGroup extends WI.AuditTestBase
+{
+    constructor(name, tests, {description} = {})
+    {
+        console.assert(Array.isArray(tests));
+
+        super(name, {description});
+
+        this._tests = tests;
+
+        for (let test of this._tests) {
+            test.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this);
+            test.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this);
+        }
+    }
+
+    // Static
+
+    static fromPayload(payload)
+    {
+        if (typeof payload !== "object" || payload === null)
+            return null;
+
+        let {type, name, tests, description} = payload;
+
+        if (type !== WI.AuditTestGroup.TypeIdentifier)
+            return null;
+
+        if (typeof name !== "string")
+            return null;
+
+        if (!Array.isArray(tests))
+            return null;
+
+        tests = tests.map((test) => WI.AuditTestCase.fromPayload(test) || WI.AuditTestGroup.fromPayload(test));
+        tests = tests.filter((test) => !!test);
+        if (!tests.length)
+            return null;
+
+        let options = {};
+        if (typeof description === "string")
+            options.description = description;
+
+        return new WI.AuditTestGroup(name, tests, options);
+    }
+
+    // Public
+
+    get tests() { return this._tests; }
+
+    stop()
+    {
+        // Called from WI.AuditManager.
+
+        for (let test of this._tests)
+            test.stop();
+
+        super.stop();
+    }
+
+    clearResult(options = {})
+    {
+        let cleared = !!this._result;
+        for (let test of this._tests) {
+            if (test.clearResult(options))
+                cleared = true;
+        }
+
+        return super.clearResult({
+            ...options,
+            suppressResultClearedEvent: !cleared,
+        });
+    }
+
+    toJSON()
+    {
+        let json = super.toJSON();
+        json.tests = this._tests.map((testCase) => testCase.toJSON());
+        return json;
+    }
+
+    // Protected
+
+    async run()
+    {
+        let count = this._tests.length;
+        for (let index = 0; index < count && this._runningState === WI.AuditManager.RunningState.Active; ++index) {
+            let test = this._tests[index];
+
+            await test.start();
+
+            if (test instanceof WI.AuditTestCase)
+                this.dispatchEventToListeners(WI.AuditTestBase.Event.Progress, {index, count});
+        }
+
+        this._updateResult();
+    }
+
+    // Private
+
+    _updateResult()
+    {
+        let results = this._tests.map((test) => test.result).filter((result) => !!result);
+        if (!results.length)
+            return;
+
+        this._result = new WI.AuditTestGroupResult(this.name, results, {
+            description: this.description,
+        });
+    }
+
+    _handleTestCompleted(event)
+    {
+        if (this._runningState === WI.AuditManager.RunningState.Active)
+            return;
+
+        this._updateResult();
+        this.dispatchEventToListeners(WI.AuditTestBase.Event.Completed);
+    }
+
+    _handleTestProgress(event)
+    {
+        if (this._runningState !== WI.AuditManager.RunningState.Active)
+            return;
+
+        let walk = (tests) => {
+            let count = 0;
+            for (let test of tests) {
+                if (test instanceof WI.AuditTestCase)
+                    ++count;
+                else if (test instanceof WI.AuditTestGroup)
+                    count += walk(test.tests);
+            }
+            return count;
+        };
+
+        this.dispatchEventToListeners(WI.AuditTestBase.Event.Progress, {
+            index: event.data.index + walk(this.tests.slice(0, this.tests.indexOf(event.target))),
+            count: walk(this.tests),
+        });
+    }
+};
+
+WI.AuditTestGroup.TypeIdentifier = "test-group";
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestGroupResultjs"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroupResult.js (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroupResult.js                         (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroupResult.js    2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,122 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.AuditTestGroupResult = class AuditTestGroupResult extends WI.AuditTestResultBase
+{
+    constructor(name, results, {description} = {})
+    {
+        console.assert(Array.isArray(results));
+
+        super(name, {description});
+
+        this._results = results;
+    }
+
+    // Static
+
+    static fromPayload(payload)
+    {
+        if (typeof payload !== "object" || payload === null)
+            return null;
+
+        let {type, name, description, results} = payload;
+
+        if (type !== WI.AuditTestGroupResult.TypeIdentifier)
+            return null;
+
+        if (typeof name !== "string")
+            return null;
+
+        if (!Array.isArray(results))
+            return null;
+
+        results = results.map((result) => WI.AuditTestGroupResult.fromPayload(result) || WI.AuditTestCaseResult.fromPayload(result));
+        results = results.filter((result) => !!result);
+        if (!results.length)
+            return null;
+
+        let options = {};
+        if (typeof description === "string")
+            options.description = description;
+
+        return new WI.AuditTestGroupResult(name, results, options);
+    }
+
+    // Public
+
+    get results() { return this._results; }
+
+    get levelCounts()
+    {
+        let counts = {};
+        for (let level of Object.values(WI.AuditTestCaseResult.Level))
+            counts[level] = 0;
+
+        for (let result of this._results) {
+            if (result instanceof WI.AuditTestCaseResult)
+                ++counts[result.level];
+            else if (result instanceof WI.AuditTestGroupResult) {
+                for (let [level, count] of Object.entries(result.levelCounts))
+                    counts[level] += count;
+            }
+        }
+
+        return counts;
+    }
+
+    get didPass()
+    {
+        return this._results.some((result) => result.didPass);
+    }
+
+    get didWarn()
+    {
+        return this._results.some((result) => result.didWarn);
+    }
+
+    get didFail()
+    {
+        return this._results.some((result) => result.didFail);
+    }
+
+    get didError()
+    {
+        return this._results.some((result) => result.didError);
+    }
+
+    get unsupported()
+    {
+        return this._results.some((result) => result.unsupported);
+    }
+
+    toJSON()
+    {
+        let json = super.toJSON();
+        json.results = this._results.map((result) => result.toJSON());
+        return json;
+    }
+};
+
+WI.AuditTestGroupResult.TypeIdentifier = "test-group-result";
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestResultBasejsfromrev237612trunkSourceWebInspectorUIUserInterfaceModelsAuditTestSuitejs"></a>
<div class="copfile"><h4>Copied: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestResultBase.js (from rev 237612, trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestSuite.js) (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestResultBase.js                          (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestResultBase.js     2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,87 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.AuditTestResultBase = class AuditTestResultBase
+{
+    constructor(name, {description} = {})
+    {
+        console.assert(typeof name === "string");
+        console.assert(!description || typeof description === "string");
+
+        this._name = name;
+        this._description = description || null;
+    }
+
+    // Public
+
+    get name() { return this._name; }
+    get description() { return this._description; }
+
+    get result()
+    {
+        return this;
+    }
+
+    get didPass()
+    {
+        throw WI.NotImplementedError.subclassMustOverride();
+    }
+
+    get didWarn()
+    {
+        throw WI.NotImplementedError.subclassMustOverride();
+    }
+
+    get didFail()
+    {
+        throw WI.NotImplementedError.subclassMustOverride();
+    }
+
+    get didError()
+    {
+        throw WI.NotImplementedError.subclassMustOverride();
+    }
+
+    get unsupported()
+    {
+        throw WI.NotImplementedError.subclassMustOverride();
+    }
+
+    saveIdentityToCookie(cookie)
+    {
+        cookie["audit-" + this.constructor.TypeIdentifier + "-name"] = this._name;
+    }
+
+    toJSON()
+    {
+        let json = {
+            type: this.constructor.TypeIdentifier,
+            name: this._name,
+        };
+        if (this._description)
+            json.description = this._description;
+        return json;
+    }
+};
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestSuitejs"></a>
<div class="delfile"><h4>Deleted: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestSuite.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestSuite.js       2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestSuite.js  2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -1,70 +0,0 @@
</span><del>-/*
- * Copyright (C) 2018 Apple Inc. All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
- * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
- * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-WI.AuditTestSuite = class AuditTestSuite extends WI.Object
-{
-    constructor(identifier, name)
-    {
-        super();
-        this._id = Symbol(identifier);
-        this._name = name;
-        this._testCases = new Map;
-
-        this._buildTestCasesFromDescriptors();
-    }
-
-    static testCaseDescriptors() { throw WI.NotImplementedError.subclassMustOverride(); }
-
-    // Public
-
-    get id() { return this._id; }
-    get name() { return this._name; }
-    get testCases() {
-        return [...this._testCases.values()];
-    }
-
-    // Private
-
-    _buildTestCasesFromDescriptors()
-    {
-        for (let descriptor of this.constructor.testCaseDescriptors()) {
-            if (typeof descriptor.name !== "string" || !descriptor.name)
-                throw new Error("Test name must be a valid string.");
-
-            let {name, test, setup, tearDown, errorDetails} = descriptor;
-
-            if (!(test instanceof Function) || test[Symbol.toStringTag] !== "AsyncFunction")
-                throw new Error("Test function must be an async function.");
-
-            let testCaseInstance = new WI.AuditTestCase(this, name, test, setup, tearDown, errorDetails);
-
-            this._testCases.set(testCaseInstance.id, testCaseInstance);
-        }
-    }
-};
-
-WI.AuditTestSuite.Event = {
-    NewAuditResultAvailable: Symbol("new-audit-result-available")
-};
</del></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceTestTestjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Test/Test.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Test/Test.js   2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Test/Test.js      2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -63,6 +63,7 @@
</span><span class="cx">     this.workerManager = new WI.WorkerManager;
</span><span class="cx">     this.domDebuggerManager = new WI.DOMDebuggerManager;
</span><span class="cx">     this.canvasManager = new WI.CanvasManager;
</span><ins>+    this.auditManager = new WI.AuditManager;
</ins><span class="cx"> 
</span><span class="cx">     document.addEventListener("DOMContentLoaded", this.contentLoaded);
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceTesthtml"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Test.html (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Test.html      2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Test.html 2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -199,10 +199,12 @@
</span><span class="cx">     <script src="Models/WrappedPromise.js"></script>
</span><span class="cx">     <script src="Models/XHRBreakpoint.js"></script>
</span><span class="cx"> 
</span><del>-    <script src="Models/AuditReport.js"></script>
-    <script src="Models/AuditResult.js"></script>
</del><ins>+    <script src="Models/AuditTestBase.js"></script>
</ins><span class="cx">     <script src="Models/AuditTestCase.js"></script>
</span><del>-    <script src="Models/AuditTestSuite.js"></script>
</del><ins>+    <script src="Models/AuditTestGroup.js"></script>
+    <script src="Models/AuditTestResultBase.js"></script>
+    <script src="Models/AuditTestCaseResult.js"></script>
+    <script src="Models/AuditTestGroupResult.js"></script>
</ins><span class="cx"> 
</span><span class="cx">     <script src="Proxies/FormatterWorkerProxy.js"></script>
</span><span class="cx">     <script src="Proxies/HeapSnapshotDiffProxy.js"></script>
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditNavigationSidebarPanelcssfromrev237612trunkSourceWebInspectorUIUserInterfaceModelsAuditResultjs"></a>
<div class="copfile"><h4>Copied: trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.css (from rev 237612, trunk/Source/WebInspectorUI/UserInterface/Models/AuditResult.js) (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.css                          (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.css     2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,28 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.sidebar > .panel.navigation.audit > .content {
+    top: var(--navigation-bar-height);
+}
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditNavigationSidebarPaneljs"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js                           (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js      2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,190 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.AuditNavigationSidebarPanel = class AuditNavigationSidebarPanel extends WI.NavigationSidebarPanel
+{
+    constructor()
+    {
+        super("audit", WI.UIString("Audits"));
+    }
+
+    // Public
+
+    showDefaultContentView()
+    {
+        let contentView = new WI.ContentView;
+
+        let contentPlaceholder = WI.createMessageTextView(WI.UIString("No audit selected"));
+        contentView.element.appendChild(contentPlaceholder);
+
+        let importNavigationItem = new WI.ButtonNavigationItem("import-audit", WI.UIString("Import"), "Images/Import.svg", 15, 15);
+        importNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+        importNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, () => {
+            WI.auditManager.import();
+        });
+
+        let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to import a test or result file"), importNavigationItem);
+        contentPlaceholder.appendChild(importHelpElement);
+
+        this.contentBrowser.showContentView(contentView);
+    }
+
+    // Protected
+
+    initialLayout()
+    {
+        super.initialLayout();
+
+        this.contentTreeOutline.allowsRepeatSelection = false;
+
+        this._resultsFolderTreeElement = new WI.FolderTreeElement(WI.UIString("Results"));
+        this.contentTreeOutline.appendChild(this._resultsFolderTreeElement);
+        this._resultsFolderTreeElement.hidden = true;
+        this._resultsFolderTreeElement.expand();
+
+        let navigationBar = new WI.NavigationBar;
+
+        this._startStopButtonNavigationItem = new WI.ToggleButtonNavigationItem("audit-start-stop", WI.UIString("Start"), WI.UIString("Stop"), "Images/AuditStart.svg", "Images/AuditStop.svg", 13, 13);
+        this._startStopButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+        this._updateStartStopButtonNavigationItemState();
+        this._startStopButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleStartStopButtonNavigationItemClicked, this);
+        navigationBar.addNavigationItem(this._startStopButtonNavigationItem);
+
+        navigationBar.addNavigationItem(new WI.DividerNavigationItem);
+
+        let importButtonNavigationItem = new WI.ButtonNavigationItem("audit-import", WI.UIString("Import"), "Images/Import.svg", 15, 15);
+        importButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+        importButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
+        importButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
+        navigationBar.addNavigationItem(importButtonNavigationItem);
+
+        this.addSubview(navigationBar);
+
+        for (let test of WI.auditManager.tests)
+            this._addTest(test);
+
+        for (let result of WI.auditManager.results)
+            this._addResult(result);
+
+        WI.auditManager.addEventListener(WI.AuditManager.Event.TestAdded, this._handleAuditTestAdded, this);
+        WI.auditManager.addEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditTestCompleted, this);
+        WI.auditManager.addEventListener(WI.AuditManager.Event.TestScheduled, this._handleAuditTestScheduled, this);
+
+        this.contentTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
+    }
+
+    closed()
+    {
+        super.closed();
+
+        WI.auditManager.removeEventListener(null, null, this);
+    }
+
+    // Private
+
+    _addTest(test)
+    {
+        this._updateStartStopButtonNavigationItemState();
+
+        this.contentTreeOutline.insertChild(new WI.AuditTreeElement(test), this.contentTreeOutline.children.indexOf(this._resultsFolderTreeElement));
+
+        this._resultsFolderTreeElement.hidden = !this._resultsFolderTreeElement.children.length;
+    }
+
+    _addResult(result, index)
+    {
+        this._updateStartStopButtonNavigationItemState();
+
+        this._resultsFolderTreeElement.hidden = false;
+
+        let resultFolderTreeElement = new WI.FolderTreeElement(WI.UIString("Run %d").format(index + 1));
+        if (result instanceof WI.AuditTestResultBase) {
+            resultFolderTreeElement.subtitle = WI.UIString("Imported");
+            result = [result];
+        }
+        this._resultsFolderTreeElement.appendChild(resultFolderTreeElement);
+
+        for (let resultItem of result)
+            resultFolderTreeElement.appendChild(new WI.AuditTreeElement(resultItem));
+    }
+
+    _updateStartStopButtonNavigationItemState()
+    {
+        this._startStopButtonNavigationItem.toggled = WI.auditManager.runningState !== WI.AuditManager.RunningState.Inactive;
+        this._startStopButtonNavigationItem.enabled = WI.auditManager.tests.length && WI.auditManager.runningState !== WI.AuditManager.RunningState.Stopping;
+    }
+
+    _handleAuditTestAdded(event)
+    {
+        this._addTest(event.data.test);
+    }
+
+    _handleAuditTestCompleted(event)
+    {
+        let {result, index} = event.data;
+        this._addResult(result, index);
+    }
+
+    _handleAuditTestScheduled(event)
+    {
+        this._updateStartStopButtonNavigationItemState();
+    }
+
+    _treeSelectionDidChange(event)
+    {
+        if (!this.selected)
+            return;
+
+        let treeElement = event.data.selectedElement;
+        if (!treeElement || treeElement instanceof WI.FolderTreeElement) {
+            this.showDefaultContentView();
+            return;
+        }
+
+        let representedObject = treeElement.representedObject;
+        if (representedObject instanceof WI.AuditTestCase || representedObject instanceof WI.AuditTestGroup
+            || representedObject instanceof WI.AuditTestCaseResult || representedObject instanceof WI.AuditTestGroupResult) {
+            WI.showRepresentedObject(representedObject);
+            return;
+        }
+
+        console.error("Unknown tree element", treeElement);
+    }
+
+    _handleStartStopButtonNavigationItemClicked(event)
+    {
+        if (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive)
+            WI.auditManager.start();
+        else if (WI.auditManager.runningState === WI.AuditManager.RunningState.Active)
+            WI.auditManager.stop();
+
+        this._updateStartStopButtonNavigationItemState();
+    }
+
+    _handleImportButtonNavigationItemClicked(event)
+    {
+        WI.auditManager.import();
+    }
+};
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTabContentViewjs"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTabContentView.js (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTabContentView.js                           (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTabContentView.js      2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,104 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.AuditTabContentView = class AuditTabContentView extends WI.ContentBrowserTabContentView
+{
+    constructor()
+    {
+        super("audit", ["audit"], WI.GeneralTabBarItem.fromTabInfo(WI.AuditTabContentView.tabInfo()), WI.AuditNavigationSidebarPanel);
+
+        this._startStopShortcut = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.Space, this._handleSpace.bind(this));
+        this._startStopShortcut.implicitlyPreventsDefault = false;
+        this._startStopShortcut.disabled = true;
+    }
+
+    // Static
+
+    static tabInfo()
+    {
+        return {
+            image: "Images/Audit.svg",
+            title: WI.UIString("Audit"),
+        };
+    }
+
+    static isTabAllowed()
+    {
+        return !!window.RuntimeAgent && WI.settings.experimentalEnableAuditTab.value;
+    }
+
+    // Public
+
+    get type()
+    {
+        return WI.AuditTabContentView.Type;
+    }
+
+    get supportsSplitContentBrowser()
+    {
+        return true;
+    }
+
+    canShowRepresentedObject(representedObject)
+    {
+        return representedObject instanceof WI.AuditTestCase
+            || representedObject instanceof WI.AuditTestGroup
+            || representedObject instanceof WI.AuditTestCaseResult
+            || representedObject instanceof WI.AuditTestGroupResult;
+    }
+
+    shown()
+    {
+        super.shown();
+
+        this._startStopShortcut.disabled = false;
+    }
+
+    hidden()
+    {
+        this._startStopShortcut.disabled = true;
+
+        super.hidden();
+    }
+
+    // Private
+
+    _handleSpace(event)
+    {
+        if (WI.isEventTargetAnEditableField(event))
+            return;
+
+        if (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive)
+            WI.auditManager.start(this.contentBrowser.currentRepresentedObjects);
+        else if (WI.auditManager.runningState === WI.AuditManager.RunningState.Active)
+            WI.auditManager.stop();
+        else
+            return;
+
+        event.preventDefault();
+    }
+};
+
+WI.AuditTabContentView.Type = "audit";
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestCaseContentViewcss"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css                             (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css        2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,105 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.content-view-container > .content-view.audit-test-case > header {
+    position: -webkit-sticky;
+    top: -1px;
+    z-index: var(--z-index-header);
+    margin-top: -1px;
+    background-color: var(--audit-test-header-background-color);
+    border-top: 1px solid var(--border-color);
+    -webkit-backdrop-filter: blur(20px);
+}
+
+.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view):first-child {
+    margin-top: var(--audit-test-vertical-space);
+}
+
+.content-view.audit-test-case > header > h1 {
+    display: flex;
+    align-items: center;
+}
+
+.content-view.audit-test-case > header > h1 > img {
+    width: 1em;
+    height: 1em;
+    min-width: 16px;
+    min-height: 16px;
+    -webkit-margin-end: 4px;
+}
+
+.content-view.audit-test-case > section > :not(.message-text-view) {
+    margin-right: var(--audit-test-horizontal-space);
+    margin-left: var(--audit-test-horizontal-space);
+}
+
+.content-view.audit-test-case > section > :not(.message-text-view):last-child {
+    margin-bottom: var(--audit-test-vertical-space);
+}
+
+.content-view.audit-test-case > section > :not(.message-text-view) + :not(.message-text-view) {
+    margin-top: var(--audit-test-vertical-space);
+}
+
+.content-view.audit-test-case > section h1 {
+    margin-bottom: 4px;
+}
+
+.content-view.audit-test-case > section table {
+    border-collapse: collapse;
+}
+
+.content-view.audit-test-case > section table > tr + tr > td {
+    padding-top: 2px;
+}
+
+.content-view.audit-test-case > section table > tr > td > :not(.tree-outline) {
+    -webkit-user-select: text;
+}
+
+.content-view.audit-test-case > section table > tr > td:first-child {
+    -webkit-padding-start: calc(8px - 0.375em);
+    font-family: -webkit-system-font, sans-serif;
+    font-size: 11px;
+    font-variant-numeric: tabular-nums;
+    text-align: end;
+    vertical-align: top;
+    color: var(--console-secondary-text-color);
+}
+
+.content-view.audit-test-case > section > .dom-nodes > table > tr > td:first-child {
+    position: relative;
+    top: -1px;
+}
+
+.content-view.audit-test-case > section .CodeMirror {
+    width: 100%;
+    height: auto;
+}
+
+.content-view.audit-test-case > section .mark {
+    background-color: hsla(53, 83%, 53%, 0.2);
+    border-bottom: 1px solid hsl(47, 82%, 60%);
+}
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestCaseContentViewjs"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js                              (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js 2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,198 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.AuditTestCaseContentView = class AuditTestCaseContentView extends WI.AuditTestContentView
+{
+    constructor(representedObject)
+    {
+        console.assert(representedObject instanceof WI.AuditTestCase || representedObject instanceof WI.AuditTestCaseResult);
+
+        super(representedObject);
+
+        this.element.classList.add("audit-test-case");
+    }
+
+    // Protected
+
+    initialLayout()
+    {
+        super.initialLayout();
+
+        let nameElement = this.headerView.element.appendChild(document.createElement("h1"));
+
+        this._resultImageElement = nameElement.appendChild(document.createElement("img"));
+
+        nameElement.appendChild(document.createTextNode(this.representedObject.name));
+
+        if (this.representedObject.description) {
+            let descriptionElement = this.headerView.element.appendChild(document.createElement("p"));
+            descriptionElement.textContent = this.representedObject.description;
+        }
+    }
+
+    layout()
+    {
+        if (this.layoutReason !== WI.View.LayoutReason.Dirty)
+            return;
+
+        super.layout();
+
+        this._resultImageElement.src = "Images/AuditTestNoResult.svg";
+
+        this.contentView.element.removeChildren();
+
+        let result = this.representedObject.result;
+        if (!result) {
+            if (this.representedObject.runningState === WI.AuditManager.RunningState.Inactive)
+                this.showNoResultPlaceholder();
+            else if (this.representedObject.runningState === WI.AuditManager.RunningState.Active)
+                this.showRunningPlaceholder();
+            else if (this.representedObject.runningState === WI.AuditManager.RunningState.Stopping)
+                this.showStoppingPlaceholder();
+
+            return;
+        }
+
+        if (result.didError)
+            this._resultImageElement.src = "Images/AuditTestError.svg";
+        else if (result.didFail)
+            this._resultImageElement.src = "Images/AuditTestFail.svg";
+        else if (result.didWarn)
+            this._resultImageElement.src = "Images/AuditTestWarn.svg";
+        else if (result.didPass)
+            this._resultImageElement.src = "Images/AuditTestPass.svg";
+        else if (result.unsupported)
+            this._resultImageElement.src = "Images/AuditTestUnsupported.svg";
+
+        let resultData = result.data;
+
+        if (resultData.domNodes && resultData.domNodes.length) {
+            let domNodesContainer = this.contentView.element.appendChild(document.createElement("div"));
+            domNodesContainer.classList.add("dom-nodes");
+
+            let domNodeText = domNodesContainer.appendChild(document.createElement("h1"));
+            domNodeText.textContent = WI.UIString("DOM Nodes:");
+
+            let tableContainer = domNodesContainer.appendChild(document.createElement("table"));
+
+            resultData.domNodes.forEach((domNode, index) => {
+                let rowElement = tableContainer.appendChild(document.createElement("tr"));
+
+                let indexElement = rowElement.appendChild(document.createElement("td"));
+                indexElement.textContent = index + 1;
+
+                let dataElement = rowElement.appendChild(document.createElement("td"));
+
+                if (domNode instanceof WI.DOMNode) {
+                    let treeOutline = new WI.DOMTreeOutline;
+                    treeOutline.setVisible(true);
+                    treeOutline.rootDOMNode = domNode;
+
+                    let rootTreeElement = treeOutline.children[0];
+                    if (!rootTreeElement.hasChildren)
+                        treeOutline.element.classList.add("single-node");
+
+                    if (resultData.domAttributes) {
+                        for (let domAttribute of resultData.domAttributes) {
+                            rootTreeElement.highlightAttribute(domAttribute);
+                            rootTreeElement.updateTitle();
+                        }
+                    }
+
+                    dataElement.appendChild(treeOutline.element);
+                } else if (typeof domNode === "string") {
+                    let codeMirror = WI.CodeMirrorEditor.create(dataElement.appendChild(document.createElement("code")), {
+                        mode: "css",
+                        readOnly: true,
+                        lineWrapping: true,
+                        showWhitespaceCharacters: WI.settings.showWhitespaceCharacters.value,
+                        styleSelectedText: true,
+                    });
+                    codeMirror.setValue(domNode);
+
+                    if (resultData.domAttributes) {
+                        for (let domAttribute of resultData.domAttributes) {
+                            let regex = null;
+                            if (domAttribute === "id")
+                                regex = /(\#[^\#|\.|\[|\s|$]+)/g;
+                            else if (domAttribute === "class")
+                                regex = /(\.[^\#|\.|\[|\s|$]+)/g;
+                            else
+                                regex = new RegExp(`\\[\\s*(${domAttribute})\\s*=`, "g");
+
+                            while (true) {
+                                let match = regex.exec(domNode);
+                                if (!match)
+                                    break;
+
+                                let start = match.index + match[0].indexOf(match[1]);
+                                codeMirror.markText({line: 0, ch: start}, {line: 0, ch: start + match[1].length}, {className: "mark"});
+                            }
+                        }
+                    }
+                }
+            });
+        }
+
+        if (resultData.errors && resultData.errors.length) {
+            let errorContainer = this.contentView.element.appendChild(document.createElement("div"));
+            errorContainer.classList.add("errors");
+
+            let errorText = errorContainer.appendChild(document.createElement("h1"));
+            errorText.textContent = WI.UIString("Errors:");
+
+            let tableContainer = errorContainer.appendChild(document.createElement("table"));
+
+            resultData.errors.forEach((error, index) => {
+                let rowElement = tableContainer.appendChild(document.createElement("tr"));
+
+                let indexElement = rowElement.appendChild(document.createElement("td"));
+                indexElement.textContent = index + 1;
+
+                let dataElement = rowElement.appendChild(document.createElement("td"));
+
+                let errorElement = dataElement.appendChild(document.createElement("div"));
+                errorElement.classList.add("error");
+                errorElement.textContent = error;
+            });
+        }
+
+        if (!this.contentView.element.children.length)
+            this.showNoResultDataPlaceholder();
+    }
+
+    showRunningPlaceholder()
+    {
+        if (!this.placeholderElement || !this.placeholderElement.__placeholderRunning) {
+            this.placeholderElement = WI.createMessageTextView(WI.UIString("Running the “%s“ audit").format(this.representedObject.name));
+            this.placeholderElement.__placeholderRunning = true;
+
+            let spinner = new WI.IndeterminateProgressSpinner;
+            this.placeholderElement.appendChild(spinner.element);
+        }
+
+        super.showRunningPlaceholder();
+    }
+};
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestContentViewcss"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.css (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.css                         (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.css    2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,92 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.content-view-container > .content-view.audit-test {
+    overflow-y: scroll;
+}
+
+.content-view-container > .content-view.audit-test > header {
+    padding-top: calc(var(--audit-test-vertical-space) / 5 * 8);
+    padding-bottom: calc(var(--audit-test-vertical-space) / 5 * 8);
+    border-bottom: 1px solid var(--border-color);
+}
+
+.content-view-container > .content-view.audit-test > header h1 {
+    font-size: 2.8em;
+}
+
+.content-view-container > .content-view.audit-test > header p {
+    font-size: 1.25em;
+}
+
+.content-view.audit-test {
+    --audit-test-vertical-space: 10px;
+    --audit-test-horizontal-space: 20px;
+    --audit-test-header-background-color: hsla(0, 0%, 98%, 0.7);
+}
+
+.content-view.audit-test h1 {
+    margin: 0;
+}
+
+.content-view.audit-test > header {
+    padding: var(--audit-test-vertical-space) var(--audit-test-horizontal-space);
+}
+
+.content-view.audit-test > header p {
+    margin: 4px 0 0;
+}
+
+.content-view.audit-test .audit-test.filtered,
+.content-view.audit-test .audit-test .message-text-view {
+    display: none;
+}
+
+.content-view.audit-test > section {
+    position: relative;
+}
+
+.content-view.audit-test > section > .message-text-view {
+    background-color: var(--background-color-content);
+}
+
+.content-view.audit-test.showing-placeholder {
+    display: flex;
+    flex-direction: column;
+}
+
+.content-view.audit-test.showing-placeholder > section {
+    flex-grow: 1;
+}
+
+.content-view.audit-test.showing-placeholder > section > :not(.message-text-view) {
+    display: none;
+}
+
+@media (prefers-dark-interface) {
+    .content-view.audit-test {
+        --audit-test-header-background-color: hsla(0, 0%, 23%, 0.7);
+    }
+}
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestContentViewjs"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js                          (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js     2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,290 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.AuditTestContentView = class AuditTestContentView extends WI.ContentView
+{
+    constructor(representedObject)
+    {
+        console.assert(representedObject instanceof WI.AuditTestBase || representedObject instanceof WI.AuditTestResultBase);
+
+        super(representedObject);
+
+        this.element.classList.add("audit-test");
+
+        this._exportButtonNavigationItem = new WI.ButtonNavigationItem("audit-export", WI.UIString("Export"), "Images/Export.svg", 15, 15);
+        this._exportButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+        this._exportButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
+        this._exportButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleExportButtonNavigationItemClicked, this);
+        this._updateExportButtonNavigationItemState();
+
+        this._headerView = new WI.View(document.createElement("header"));
+        this._contentView = new WI.View(document.createElement("section"));
+        this._placeholderElement = null;
+
+        this._shownResult = null;
+    }
+
+    // Public
+
+    get navigationItems()
+    {
+        return [this._exportButtonNavigationItem];
+    }
+
+    // Protected
+
+    get headerView() { return this._headerView; }
+    get contentView() { return this._contentView; }
+
+    get supportsSave()
+    {
+        return !!this.representedObject.result;
+    }
+
+    get saveData()
+    {
+        return {customSaveHandler: () => { this._exportAudit(); }};
+    }
+
+    get result()
+    {
+        if (this.representedObject instanceof WI.AuditTestBase)
+            return this.representedObject;
+        return this.representedObject.result;
+    }
+
+    initialLayout()
+    {
+        super.initialLayout();
+
+        this.addSubview(this._headerView);
+        this.addSubview(this._contentView);
+    }
+
+    layout()
+    {
+        super.layout();
+
+        this.hidePlaceholder();
+        this._updateExportButtonNavigationItemState();
+    }
+
+    shown()
+    {
+        super.shown();
+
+        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.Scheduled, this._handleTestChanged, this);
+            this.representedObject.addEventListener(WI.AuditTestBase.Event.Stopping, this._handleTestChanged, this);
+        }
+    }
+
+    hidden()
+    {
+        if (this.representedObject instanceof WI.AuditTestBase)
+            this.representedObject.removeEventListener(null, null, this);
+
+        super.hidden();
+    }
+
+    get placeholderElement()
+    {
+        return this._placeholderElement;
+    }
+
+    set placeholderElement(placeholderElement)
+    {
+        this.hidePlaceholder();
+
+        this._placeholderElement = placeholderElement;
+    }
+
+    showRunningPlaceholder()
+    {
+        // Overridden by sub-classes.
+
+        console.assert(this.placeholderElement);
+
+        this._showPlaceholder();
+    }
+
+    showStoppingPlaceholder()
+    {
+        if (!this.placeholderElement || !this.placeholderElement.__placeholderStopping) {
+            this.placeholderElement = WI.createMessageTextView(WI.UIString("Stopping the “%s“ audit").format(this.representedObject.name));
+            this.placeholderElement.__placeholderStopping = true;
+
+            let spinner = new WI.IndeterminateProgressSpinner;
+            this.placeholderElement.appendChild(spinner.element);
+        }
+
+        this._showPlaceholder();
+    }
+
+    showNoResultPlaceholder()
+    {
+        if (!this.placeholderElement || !this.placeholderElement.__placeholderNoResult) {
+            this.placeholderElement = WI.createMessageTextView(WI.UIString("No Result"));
+            this.placeholderElement.__placeholderNoResult = true;
+
+            let startNavigationItem = new WI.ButtonNavigationItem("run-audit", WI.UIString("Start"), "Images/AuditStart.svg", 15, 15);
+            startNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+            startNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, () => {
+                WI.auditManager.start([this.representedObject]);
+            });
+
+            let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to start running the audit"), startNavigationItem);
+            this.placeholderElement.appendChild(importHelpElement);
+        }
+
+        this._showPlaceholder();
+    }
+
+    showNoResultDataPlaceholder()
+    {
+        if (!this.placeholderElement || !this.placeholderElement.__placeholderNoResultData) {
+            let result = this.representedObject.result;
+            if (!result) {
+                this.showNoResultPlaceholder();
+                return;
+            }
+
+            let message = null;
+            if (result.didError)
+                message = WI.UIString("The “%s“ audit threw an error");
+            else if (result.didFail)
+                message = WI.UIString("The “%s“ audit failed");
+            else if (result.didWarn)
+                message = WI.UIString("The “%s“ audit warned");
+            else if (result.didPass)
+                message = WI.UIString("The “%s“ audit passed");
+            else if (result.unsupported)
+                message = WI.UIString("The “%s“ audit is unsupported");
+            else {
+                console.error("Unknown result", result);
+                return;
+            }
+
+            this.placeholderElement = WI.createMessageTextView(message.format(this.representedObject.name), result.didError);
+            this.placeholderElement.__placeholderNoResultData = true;
+        }
+
+        this._showPlaceholder();
+    }
+
+    showFilteredPlaceholder()
+    {
+        if (!this.placeholderElement || !this.placeholderElement.__placeholderFiltered) {
+            this.placeholderElement = WI.createMessageTextView(WI.UIString("No Filter Results"));
+            this.placeholderElement.__placeholderFiltered = true;
+
+            let buttonElement = this.placeholderElement.appendChild(document.createElement("button"));
+            buttonElement.textContent = WI.UIString("Clear filters");
+            buttonElement.addEventListener("click", () => {
+                this.resetFilter();
+                this.needsLayout();
+            });
+        }
+
+        this._showPlaceholder();
+    }
+
+    hidePlaceholder()
+    {
+        this.element.classList.remove("showing-placeholder");
+        if (this.placeholderElement)
+            this.placeholderElement.remove();
+    }
+
+    applyFilter(levels)
+    {
+        let hasMatch = false;
+        for (let view of this.contentView.subviews) {
+            let matches = view.applyFilter(levels);
+            view.element.classList.toggle("filtered", !matches);
+
+            if (matches)
+                hasMatch = true;
+        }
+
+        this.element.classList.toggle("no-matches", !hasMatch);
+
+        if (!Array.isArray(levels))
+            return true;
+
+        let result = this.representedObject.result;
+        if (!result)
+            return false;
+
+        if ((levels.includes(WI.AuditTestCaseResult.Level.Error) && result.didError)
+            || (levels.includes(WI.AuditTestCaseResult.Level.Fail) && result.didFail)
+            || (levels.includes(WI.AuditTestCaseResult.Level.Warn) && result.didWarn)
+            || (levels.includes(WI.AuditTestCaseResult.Level.Pass) && result.didPass)
+            || (levels.includes(WI.AuditTestCaseResult.Level.Unsupported) && result.unsupported)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    resetFilter()
+    {
+        for (let view of this.contentView.subviews)
+            view.element.classList.remove("filtered");
+
+        this.element.classList.remove("no-matches");
+    }
+
+    // Private
+
+    _exportAudit()
+    {
+        WI.auditManager.export(this.representedObject.result);
+    }
+
+    _updateExportButtonNavigationItemState()
+    {
+        this._exportButtonNavigationItem.enabled = !!this.representedObject.result;
+    }
+
+    _showPlaceholder()
+    {
+        this.element.classList.add("showing-placeholder");
+        this.contentView.element.appendChild(this.placeholderElement);
+    }
+
+    _handleExportButtonNavigationItemClicked(event)
+    {
+        this._exportAudit();
+    }
+
+    _handleTestChanged(event)
+    {
+        this.needsLayout();
+    }
+};
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestGroupContentViewcss"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.css (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.css                            (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.css       2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,150 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.content-view-container > .content-view.audit-test-group > header {
+    background-color: var(--background-color-content);
+}
+
+.content-view.audit-test-group > header {
+    display: flex;
+    align-items: center;
+    margin-top: -1px;
+    border-top: 1px solid var(--border-color);
+}
+
+.content-view.audit-test-group.no-matches + .audit-test-group > header {
+    border-top: none;
+}
+
+.content-view.audit-test-group > header,
+.content-view.audit-test-group:not(.filtered):last-child > header {
+    border-bottom: 1px solid var(--border-color);
+}
+
+.content-view.audit-test-group.contains-test-case > header {
+    position: -webkit-sticky;
+    top: -1px;
+    z-index: var(--z-index-header);
+    background-color: var(--audit-test-header-background-color);
+    -webkit-backdrop-filter: blur(20px);
+}
+
+.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case {
+    border-top: 1px solid var(--border-color);
+}
+
+.content-view.audit-test-group.contains-test-case:not(.contains-test-group) > section,
+.content-view.audit-test-group.contains-test-case.contains-test-group > section > .audit-test-case {
+    padding-right: calc(var(--audit-test-horizontal-space) / 2);
+    padding-left: calc(var(--audit-test-horizontal-space) / 2);
+}
+
+.content-view.audit-test-group > header > .information {
+    flex-grow: 1;
+}
+
+.content-view.audit-test-group > header > .information > p {
+    font-size: 1.25em;
+}
+
+.content-view.audit-test-group > header > nav {
+    display: inline-flex;
+    height: auto;
+    border-bottom: none;
+}
+
+.content-view.audit-test-group > header > nav:empty {
+    display: none;
+}
+
+.content-view.audit-test-group > header > nav:not(:empty):before {
+    content: attr(data-prefix);
+}
+
+.content-view.audit-test-group > header > nav > .scope-bar > li {
+    display: inline-flex;
+    align-items: center;
+    margin: 0 3px;
+}
+
+.content-view.audit-test-group > header > nav > .scope-bar > li:not(:hover, .selected) {
+    border: 1px solid var(--selected-background-color-unfocused);
+}
+
+.content-view.audit-test-group > header > nav > .scope-bar > li:last-child {
+    -webkit-margin-end: 0;
+}
+
+.content-view.audit-test-group > header > nav > .scope-bar > li::before {
+    width: 16px;
+    height: 16px;
+    margin-top: 1px;
+    -webkit-margin-end: 4px;
+}
+
+.content-view.audit-test-group > header > nav > .scope-bar > li.pass::before {
+    content: url(../Images/AuditTestPass.svg);
+}
+
+.content-view.audit-test-group > header > nav > .scope-bar > li.warn::before {
+    content: url(../Images/AuditTestWarn.svg);
+}
+
+.content-view.audit-test-group > header > nav > .scope-bar > li.fail::before {
+    content: url(../Images/AuditTestFail.svg);
+}
+
+.content-view.audit-test-group > header > nav > .scope-bar > li.error::before {
+    content: url(../Images/AuditTestError.svg);
+}
+
+.content-view.audit-test-group > header > nav > .scope-bar > li.unsupported::before {
+    content: url(../Images/AuditTestUnsupported.svg);
+}
+
+.content-view.audit-test-group > header > .percentage-pass {
+    -webkit-margin-start: var(--audit-test-horizontal-space);
+    width: 60px;
+    font-size: 24px;
+    text-align: center;
+    font-weight: bold;
+    opacity: 0.65;
+}
+
+.content-view.audit-test-group > header > .percentage-pass:not(:empty)::after {
+    content: "%";
+    font-size: 16px;
+    opacity: 0.75;
+}
+
+.content-view.audit-test-group > section > .audit-test-case:first-child,
+.content-view.audit-test-group > section > .audit-test-group + .audit-test-case,
+.content-view.audit-test-group > section > .audit-test-case + .audit-test-group {
+    margin-top: var(--audit-test-vertical-space);
+}
+
+.content-view.audit-test-group > section > .audit-test-case:last-child {
+    margin-bottom: var(--audit-test-vertical-space);
+}
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestGroupContentViewjs"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.js (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.js                             (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.js        2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,233 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.AuditTestGroupContentView = class AuditTestGroupContentView extends WI.AuditTestContentView
+{
+    constructor(representedObject)
+    {
+        console.assert(representedObject instanceof WI.AuditTestGroup || representedObject instanceof WI.AuditTestGroupResult);
+
+        super(representedObject);
+
+        this.element.classList.add("audit-test-group");
+        this.element.classList.toggle("contains-test-case", this._subobjects().some((test) => test instanceof WI.AuditTestCase || test instanceof WI.AuditTestCaseResult));
+        this.element.classList.toggle("contains-test-group", this._subobjects().some((test) => test instanceof WI.AuditTestGroup || test instanceof WI.AuditTestGroupResult));
+
+        this._levelScopeBar = null;
+    }
+
+    // Protected
+
+    initialLayout()
+    {
+        super.initialLayout();
+
+        let informationContainer = this.headerView.element.appendChild(document.createElement("div"));
+        informationContainer.classList.add("information");
+
+        let nameElement = informationContainer.appendChild(document.createElement("h1"));
+        nameElement.textContent = this.representedObject.name;
+
+        if (this.representedObject.description) {
+            let descriptionElement = informationContainer.appendChild(document.createElement("p"));
+            descriptionElement.textContent = this.representedObject.description;
+        }
+
+        this._levelNavigationBar = new WI.NavigationBar(document.createElement("nav"));
+        this._levelNavigationBar.element.dataset.prefix = WI.UIString("Showing:");
+        this.headerView.addSubview(this._levelNavigationBar);
+
+        this._percentageTextElement = this.headerView.element.appendChild(document.createElement("div"));
+        this._percentageTextElement.classList.add("percentage-pass");
+        this.headerView.element.appendChild(this._percentageTextElement);
+    }
+
+    layout()
+    {
+        if (this.layoutReason !== WI.View.LayoutReason.Dirty)
+            return;
+
+        super.layout();
+
+        let result = this.representedObject.result;
+        if (!result) {
+            if (this._levelScopeBar) {
+                this._levelNavigationBar.removeNavigationItem(this._levelScopeBar);
+                this._levelScopeBar = null;
+            }
+
+            this._percentageTextElement.textContent = "";
+
+            if (this.representedObject.runningState === WI.AuditManager.RunningState.Inactive)
+                this.showNoResultPlaceholder();
+            else if (this.representedObject.runningState === WI.AuditManager.RunningState.Active)
+                this.showRunningPlaceholder();
+            else if (this.representedObject.runningState === WI.AuditManager.RunningState.Stopping)
+                this.showStoppingPlaceholder();
+
+            return;
+        }
+
+        let levelCounts = result.levelCounts;
+        let totalCount = Object.values(levelCounts).reduce((accumulator, current) => accumulator + current);
+        this._percentageTextElement.textContent = Math.floor(100 * levelCounts[WI.AuditTestCaseResult.Level.Pass] / totalCount);
+
+        if (!this._levelScopeBar) {
+            let scopeBarItems = [];
+
+            let addScopeBarItem = (level, label) => {
+                let count = levelCounts[level];
+                if (isNaN(count) || count <= 0)
+                    return;
+
+                let scopeBarItem = new WI.ScopeBarItem(level, label.format(count), {
+                    className: level,
+                    exclusive: false,
+                    independent: true,
+                });
+                scopeBarItem.selected = true;
+                scopeBarItems.push(scopeBarItem);
+            };
+
+            addScopeBarItem(WI.AuditTestCaseResult.Level.Pass, WI.UIString("%d Pass"));
+            addScopeBarItem(WI.AuditTestCaseResult.Level.Warn, WI.UIString("%d Warn"));
+            addScopeBarItem(WI.AuditTestCaseResult.Level.Fail, WI.UIString("%d Fail"));
+            addScopeBarItem(WI.AuditTestCaseResult.Level.Error, WI.UIString("%d Error"));
+            addScopeBarItem(WI.AuditTestCaseResult.Level.Unsupported, WI.UIString("%d Unsupported"));
+
+            this._levelScopeBar = new WI.ScopeBar(null, scopeBarItems);
+            this._levelScopeBar.addEventListener(WI.ScopeBar.Event.SelectionChanged, this._handleLevelScopeBarSelectionChanged, this);
+            this._levelNavigationBar.addNavigationItem(this._levelScopeBar);
+        }
+
+        if (this.applyFilter())
+            this.hidePlaceholder();
+        else
+            this.showFilteredPlaceholder();
+    }
+
+    shown()
+    {
+        super.shown();
+
+        if (this.representedObject instanceof WI.AuditTestGroup) {
+            this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this);
+            this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
+        }
+
+        for (let subobject of this._subobjects()) {
+            let view = WI.ContentView.contentViewForRepresentedObject(subobject);
+            this.contentView.addSubview(view);
+            view.shown();
+        }
+    }
+
+    hidden()
+    {
+        for (let view of this.contentView.subviews)
+            view.hidden();
+
+        this.contentView.removeAllSubviews();
+
+        super.hidden();
+    }
+
+    applyFilter(levels)
+    {
+        if (this._levelScopeBar && !levels)
+            levels = this._levelScopeBar.selectedItems.map((item) => item.id);
+
+        this._updateLevelScopeBar(levels);
+
+        return super.applyFilter(levels);
+    }
+
+    resetFilter()
+    {
+        this._updateLevelScopeBar(Object.values(WI.AuditTestCaseResult.Level));
+
+        super.resetFilter();
+    }
+
+    showRunningPlaceholder()
+    {
+        if (!this.placeholderElement || !this.placeholderElement.__placeholderRunning) {
+            this.placeholderElement = WI.createMessageTextView(WI.UIString("Running the “%s“ audit").format(this.representedObject.name));
+            this.placeholderElement.__placeholderRunning = true;
+
+            this.placeholderElement.__progress = document.createElement("progress");
+            this.placeholderElement.__progress.value = 0;
+            this.placeholderElement.appendChild(this.placeholderElement.__progress);
+        }
+
+        super.showRunningPlaceholder();
+    }
+
+    // Private
+
+    _subobjects()
+    {
+        if (this.representedObject instanceof WI.AuditTestGroup)
+            return this.representedObject.tests;
+
+        if (this.representedObject instanceof WI.AuditTestGroupResult)
+            return this.representedObject.results;
+
+        console.error("Unknown representedObject", this.representedObject);
+        return [];
+    }
+
+    _updateLevelScopeBar(levels)
+    {
+        if (!this._levelScopeBar)
+            return;
+
+        for (let item of this._levelScopeBar.items)
+            item.selected = levels.includes(item.id);
+
+        for (let view of this.contentView.subviews) {
+            if (view instanceof WI.AuditTestGroupContentView)
+                view._updateLevelScopeBar(levels);
+        }
+    }
+
+    _handleTestGroupProgress(event)
+    {
+        let {index, count} = event.data;
+        if (this.placeholderElement && this.placeholderElement.__progress)
+            this.placeholderElement.__progress.value = (index + 1) / count;
+    }
+
+    _handleTestGroupScheduled(event)
+    {
+        if (this.placeholderElement && this.placeholderElement.__progress)
+            this.placeholderElement.__progress.value = 0;
+    }
+
+    _handleLevelScopeBarSelectionChanged(event)
+    {
+        this.needsLayout();
+    }
+};
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTreeElementcss"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.css (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.css                             (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.css        2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,89 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.tree-outline .item.audit > .status {
+    display: flex;
+    align-items: center;
+    width: 16px;
+    height: 16px;
+}
+
+.tree-outline .item.audit > .status > img {
+    width: 100%;
+    height: 100%;
+}
+
+.tree-outline .item.audit:matches(.test-case, .test-group) > .status:hover > img {
+    width: 75%;
+    height: 75%;
+    margin: 0 auto;
+    content: url(../Images/AuditStart.svg) !important;
+}
+
+.tree-outline .item.audit > .status:not(:hover) > img.show-on-hover,
+.tree-outline .item.audit.test-group.expanded > .status:not(:hover) {
+    opacity: 0;
+}
+
+.tree-outline .item.audit.test-group.expanded > .status:hover > :not(img),
+.tree-outline .item.audit.test-group-result.expanded > .status {
+    display: none;
+}
+
+.tree-outline .item.audit > .status > img.pass {
+    content: url(../Images/AuditTestPass.svg);
+}
+
+.tree-outline .item.audit > .status > img.warn {
+    content: url(../Images/AuditTestWarn.svg);
+}
+
+.tree-outline .item.audit > .status > img.fail {
+    content: url(../Images/AuditTestFail.svg);
+}
+
+.tree-outline .item.audit > .status > img.error {
+    content: url(../Images/AuditTestError.svg);
+}
+
+.tree-outline .item.audit > .status > img.unsupported {
+    content: url(../Images/AuditTestUnsupported.svg);
+}
+
+.audit.test-case .icon {
+    content: url(../Images/AuditTestCase.svg);
+}
+
+.audit.test-group .icon {
+    content: url(../Images/AuditTestGroup.svg);
+}
+
+.audit.test-case-result .icon {
+    content: url(../Images/AuditTestCaseResult.svg);
+}
+
+.audit.test-group-result .icon {
+    content: url(../Images/AuditTestGroupResult.svg);
+}
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTreeElementjs"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js (0 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js                              (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js 2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -0,0 +1,239 @@
</span><ins>+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.AuditTreeElement = class AuditTreeElement extends WI.GeneralTreeElement
+{
+    constructor(representedObject)
+    {
+        let isTestCase = representedObject instanceof WI.AuditTestCase;
+        let isTestGroup = representedObject instanceof WI.AuditTestGroup;
+        let isTestCaseResult = representedObject instanceof WI.AuditTestCaseResult;
+        let isTestGroupResult = representedObject instanceof WI.AuditTestGroupResult;
+        console.assert(isTestCase || isTestGroup || isTestCaseResult || isTestGroupResult);
+
+        let classNames = ["audit"];
+        if (isTestCase)
+            classNames.push("test-case");
+        else if (isTestGroup)
+            classNames.push("test-group");
+        else if (isTestCaseResult)
+            classNames.push("test-case-result");
+        else if (isTestGroupResult)
+            classNames.push("test-group-result");
+
+        let options = {
+            hasChildren: isTestGroup || isTestGroupResult,
+        };
+
+        const subtitle = null;
+        super(classNames, representedObject.name, subtitle, representedObject, options);
+    }
+
+    // Protected
+
+    onattach()
+    {
+        super.onattach();
+
+        if (this.representedObject instanceof WI.AuditTestBase) {
+            this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultCleared, this._handleTestResultCleared, this);
+
+            if (this.representedObject instanceof WI.AuditTestCase)
+                this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestCaseScheduled, this);
+            else if (this.representedObject instanceof WI.AuditTestGroup) {
+                this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
+                this.expand();
+            }
+        }
+
+        this._updateLevel();
+    }
+
+    ondetach()
+    {
+        this.representedObject.removeEventListener(null, null, this);
+
+        super.ondetach();
+    }
+
+    onpopulate()
+    {
+        super.onpopulate();
+
+        if (this.children.length && !this.shouldRefreshChildren)
+            return;
+
+        this.shouldRefreshChildren = false;
+
+        this.removeChildren();
+
+        if (this.representedObject instanceof WI.AuditTestGroup) {
+            for (let test of this.representedObject.tests)
+                this.appendChild(new WI.AuditTreeElement(test));
+        } else if (this.representedObject instanceof WI.AuditTestGroupResult) {
+            for (let result of this.representedObject.results)
+                this.appendChild(new WI.AuditTreeElement(result));
+        }
+    }
+
+    populateContextMenu(contextMenu, event)
+    {
+        if (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive) {
+            contextMenu.appendItem(WI.UIString("Start"), (event) => {
+                this._start();
+            });
+        }
+
+        contextMenu.appendSeparator();
+
+        if (this.representedObject instanceof WI.AuditTestCase || this.representedObject instanceof WI.AuditTestGroup) {
+            contextMenu.appendItem(WI.UIString("Export Test"), (event) => {
+                WI.auditManager.export(this.representedObject);
+            });
+        }
+
+        if (this.representedObject.result) {
+            contextMenu.appendItem(WI.UIString("Export Result"), (event) => {
+                WI.auditManager.export(this.representedObject.result);
+            });
+        }
+
+        contextMenu.appendSeparator();
+
+        super.populateContextMenu(contextMenu, event);
+    }
+
+    // Private
+
+    _start()
+    {
+        if (WI.auditManager.runningState !== WI.AuditManager.RunningState.Inactive)
+            return;
+
+        WI.auditManager.start([this.representedObject]);
+    }
+
+    _updateLevel()
+    {
+        let className = "show-on-hover";
+
+        let result = this.representedObject.result;
+        if (result) {
+            if (result.didError)
+                className = WI.AuditTestCaseResult.Level.Error;
+            else if (result.didFail)
+                className = WI.AuditTestCaseResult.Level.Fail;
+            else if (result.didWarn)
+                className = WI.AuditTestCaseResult.Level.Warn;
+            else if (result.didPass)
+                className = WI.AuditTestCaseResult.Level.Pass;
+            else if (result.unsupported)
+                className = WI.AuditTestCaseResult.Level.Unsupported;
+        }
+
+        this.status = document.createElement("img");
+        this.status.classList.add(className);
+
+        if (this.representedObject instanceof WI.AuditTestCase || this.representedObject instanceof WI.AuditTestGroup) {
+            this.status.title = WI.UIString("Start");
+            this.status.addEventListener("click", this._handleStatusClick.bind(this));
+        }
+    }
+
+    _showRunningSpinner()
+    {
+        if (this.representedObject.runningState === WI.AuditManager.RunningState.Inactive) {
+            this._updateLevel();
+            return;
+        }
+
+        if (!this.status || !this.status.__spinner) {
+            let spinner = new WI.IndeterminateProgressSpinner;
+            this.status = spinner.element;
+            this.status.__spinner = true;
+        }
+    }
+
+    _showRunningProgress(progress)
+    {
+        if (!this.representedObject.runningState === WI.AuditManager.RunningState.Inactive) {
+            this._updateLevel();
+            return;
+        }
+
+        if (!this.status || !this.status.__progress) {
+            this.status = document.createElement("progress");
+            this.status.__progress = true;
+        }
+
+        this.status.value = progress || 0;
+    }
+
+    _handleTestCaseCompleted(event)
+    {
+        this.representedObject.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCaseCompleted, this);
+
+        this._updateLevel();
+    }
+
+    _handleTestResultCleared(event)
+    {
+        this._updateLevel();
+    }
+
+    _handleTestCaseScheduled(event)
+    {
+        this.representedObject.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCaseCompleted, this);
+
+        this._showRunningSpinner();
+    }
+
+    _handleTestGroupCompleted(event)
+    {
+        this.representedObject.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestGroupCompleted, this);
+        this.representedObject.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this);
+
+        this._updateLevel();
+    }
+
+    _handleTestGroupProgress(event)
+    {
+        let {index, count} = event.data;
+        this._showRunningProgress((index + 1) / count);
+    }
+
+    _handleTestGroupScheduled(event)
+    {
+        this.representedObject.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestGroupCompleted, this);
+        this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this);
+
+        this._showRunningProgress();
+    }
+
+    _handleStatusClick(event)
+    {
+        this._start();
+    }
+};
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsContentViewjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js   2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ContentView.js      2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -167,6 +167,12 @@
</span><span class="cx">         if (representedObject instanceof WI.ResourceCollection)
</span><span class="cx">             return new WI.ResourceCollectionContentView(representedObject, extraArguments);
</span><span class="cx"> 
</span><ins>+        if (representedObject instanceof WI.AuditTestCase || representedObject instanceof WI.AuditTestCaseResult)
+            return new WI.AuditTestCaseContentView(representedObject, extraArguments);
+
+        if (representedObject instanceof WI.AuditTestGroup || representedObject instanceof WI.AuditTestGroupResult)
+            return new WI.AuditTestGroupContentView(representedObject, extraArguments);
+
</ins><span class="cx">         if (representedObject instanceof WI.Collection)
</span><span class="cx">             return new WI.CollectionContentView(representedObject, extraArguments);
</span><span class="cx"> 
</span><span class="lines">@@ -295,6 +301,9 @@
</span><span class="cx">             return true;
</span><span class="cx">         if (representedObject instanceof WI.Recording)
</span><span class="cx">             return true;
</span><ins>+        if (representedObject instanceof WI.AuditTestCase || representedObject instanceof WI.AuditTestGroup
+            || representedObject instanceof WI.AuditTestCaseResult || representedObject instanceof WI.AuditTestGroupResult)
+            return true;
</ins><span class="cx">         if (representedObject instanceof WI.Collection)
</span><span class="cx">             return true;
</span><span class="cx">         if (typeof representedObject === "string" || representedObject instanceof String)
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsDOMTreeElementjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js        2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js   2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -47,6 +47,7 @@
</span><span class="cx">         this._boundHighlightAnimationEnd = this._highlightAnimationEnd.bind(this);
</span><span class="cx">         this._subtreeBreakpointCount = 0;
</span><span class="cx"> 
</span><ins>+        this._highlightedAttributes = new Set;
</ins><span class="cx">         this._recentlyModifiedAttributes = [];
</span><span class="cx">         this._boundNodeChangedAnimationEnd = this._nodeChangedAnimationEnd.bind(this);
</span><span class="cx"> 
</span><span class="lines">@@ -268,6 +269,11 @@
</span><span class="cx">         this._recentlyModifiedAttributes.push({name});
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    highlightAttribute(name)
+    {
+        this._highlightedAttributes.add(name);
+    }
+
</ins><span class="cx">     showChildNode(node)
</span><span class="cx">     {
</span><span class="cx">         console.assert(!this._elementCloseTag);
</span><span class="lines">@@ -1327,6 +1333,9 @@
</span><span class="cx">             if (attribute.name === name)
</span><span class="cx">                 attribute.element = hasText ? attrValueElement : attrNameElement;
</span><span class="cx">         }
</span><ins>+
+        if (this._highlightedAttributes.has(name))
+            attrSpanElement.classList.add("highlight");
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _buildTagDOM(parentElement, tagName, isClosingTag, isDistinctTreeElement)
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsDOMTreeOutlinecss"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.css (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.css       2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.css  2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -307,6 +307,11 @@
</span><span class="cx">     animation: "dom-tree-outline-highlight-fadeout" 2s;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.tree-outline.dom li .highlight {
+    background-color: hsla(53, 83%, 53%, 0.2);
+    border-bottom: 1px solid hsl(47, 82%, 60%);
+}
+
</ins><span class="cx"> @media (prefers-dark-interface) {
</span><span class="cx">     .tree-outline.dom li.elements-drag-over .selection-area {
</span><span class="cx">         border-top-color: var(--selected-background-color);
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsDividerNavigationItemcss"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DividerNavigationItem.css (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/DividerNavigationItem.css        2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/DividerNavigationItem.css   2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -25,7 +25,7 @@
</span><span class="cx"> 
</span><span class="cx"> .navigation-bar .item.divider {
</span><span class="cx">     width: 1px;
</span><del>-
</del><ins>+    height: 100%;
</ins><span class="cx">     background-image: linear-gradient(hsl(0, 0%, 74%), hsl(0, 0%, 74%));
</span><span class="cx">     background-size: 100% 16px;
</span><span class="cx">     background-repeat: no-repeat;
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsMaincss"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/Main.css (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/Main.css 2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/Main.css    2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -199,7 +199,7 @@
</span><span class="cx">     height: 20px;
</span><span class="cx">     padding: 0 4px;
</span><span class="cx">     border-bottom: none;
</span><del>-    vertical-align: middle;
</del><ins>+    vertical-align: -3px;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> .message-text-view .navigation-item-help .navigation-bar > .item {
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsScopeBarItemjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ScopeBarItem.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/ScopeBarItem.js  2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ScopeBarItem.js     2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -25,7 +25,7 @@
</span><span class="cx"> 
</span><span class="cx"> WI.ScopeBarItem = class ScopeBarItem extends WI.Object
</span><span class="cx"> {
</span><del>-    constructor(id, label, {className, exclusive, hidden} = {})
</del><ins>+    constructor(id, label, {className, exclusive, independent, hidden} = {})
</ins><span class="cx">     {
</span><span class="cx">         super();
</span><span class="cx"> 
</span><span class="lines">@@ -39,6 +39,7 @@
</span><span class="cx">         this._id = id;
</span><span class="cx">         this._label = label;
</span><span class="cx">         this._exclusive = !!exclusive;
</span><ins>+        this._independent = !!independent;
</ins><span class="cx">         this._hidden = !!hidden;
</span><span class="cx"> 
</span><span class="cx">         this._selectedSetting = new WI.Setting("scopebaritem-" + id, false);
</span><span class="lines">@@ -83,7 +84,7 @@
</span><span class="cx">         this._selectedSetting.value = selected;
</span><span class="cx"> 
</span><span class="cx">         this.dispatchEventToListeners(WI.ScopeBarItem.Event.SelectionChanged, {
</span><del>-            extendSelection: WI.modifierKeys.metaKey && !WI.modifierKeys.ctrlKey && !WI.modifierKeys.altKey && !WI.modifierKeys.shiftKey,
</del><ins>+            extendSelection: this._independent || (WI.modifierKeys.metaKey && !WI.modifierKeys.ctrlKey && !WI.modifierKeys.altKey && !WI.modifierKeys.shiftKey),
</ins><span class="cx">         });
</span><span class="cx">     }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsSettingsTabContentViewjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js        2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SettingsTabContentView.js   2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -249,6 +249,9 @@
</span><span class="cx">             experimentalSettingsView.addSeparator();
</span><span class="cx">         }
</span><span class="cx"> 
</span><ins>+        experimentalSettingsView.addSetting(WI.UIString("Audit:"), WI.settings.experimentalEnableAuditTab, WI.UIString("Enable Audit Tab"));
+        experimentalSettingsView.addSeparator();
+
</ins><span class="cx">         experimentalSettingsView.addSetting(WI.UIString("User Interface:"), WI.settings.experimentalEnableNewTabBar, WI.UIString("Enable New Tab Bar"));
</span><span class="cx">         experimentalSettingsView.addSeparator();
</span><span class="cx"> 
</span><span class="lines">@@ -268,6 +271,7 @@
</span><span class="cx"> 
</span><span class="cx">         listenForChange(WI.settings.experimentalEnableMultiplePropertiesSelection);
</span><span class="cx">         listenForChange(WI.settings.experimentalEnableLayersTab);
</span><ins>+        listenForChange(WI.settings.experimentalEnableAuditTab);
</ins><span class="cx">         listenForChange(WI.settings.experimentalEnableNewTabBar);
</span><span class="cx"> 
</span><span class="cx">         this.addSettingsView(experimentalSettingsView);
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsToggleButtonNavigationItemjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/ToggleButtonNavigationItem.js (237612 => 237613)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/ToggleButtonNavigationItem.js    2018-10-31 00:44:31 UTC (rev 237612)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/ToggleButtonNavigationItem.js       2018-10-31 01:11:36 UTC (rev 237613)
</span><span class="lines">@@ -103,6 +103,9 @@
</span><span class="cx">             this.tooltip = this._defaultToolTip;
</span><span class="cx">             this.image = this._defaultImage;
</span><span class="cx">         }
</span><ins>+
+        if (this.buttonStyle === WI.ButtonNavigationItem.Style.Text || this.buttonStyle === WI.ButtonNavigationItem.Style.ImageAndText)
+            this.label = this.tooltip;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     // Protected
</span></span></pre>
</div>
</div>

</body>
</html>