<!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>[266317] 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/266317">266317</a></dd>
<dt>Author</dt> <dd>drousso@apple.com</dd>
<dt>Date</dt> <dd>2020-08-28 18:39:46 -0700 (Fri, 28 Aug 2020)</dd>
</dl>

<h3>Log Message</h3>
<pre>Web Inspector: Audit: should be able to create/edit imported audits
https://bugs.webkit.org/show_bug.cgi?id=215555
<rdar://problem/67255483>

Reviewed by Devin Rousso.

Source/WebInspectorUI:

Extend the existing audit edit mode (which previously only let audits be disabled/deleted)
to also allow other JSON properties of audits to be added/edited/removed, meaning that
audits can now be completely created/configured from within the Web Inspector UI. \(o.o)/

* UserInterface/Models/AuditTestBase.js:
(WI.AuditTestBase):
(WI.AuditTestBase.prototype.set name): Added.
(WI.AuditTestBase.prototype.set description): Added.
(WI.AuditTestBase.prototype.get supports): Added.
(WI.AuditTestBase.prototype.set supports): Added.
(WI.AuditTestBase.prototype.get setup): Added.
(WI.AuditTestBase.prototype.set setup): Added.
(WI.AuditTestBase.prototype.set disabled):
(WI.AuditTestBase.prototype.get editable): Added.
(WI.AuditTestBase.prototype.get default): Added.
(WI.AuditTestBase.prototype.markAsDefault): Added.
(WI.AuditTestBase.prototype.get topLevelTest): Added.
(WI.AuditTestBase.prototype.async runSetup): Added.
(WI.AuditTestBase.prototype.async start()):
(WI.AuditTestBase.prototype.stop()):
(WI.AuditTestBase.prototype.async clone): Added.
(WI.AuditTestBase.remove): Added.
(WI.AuditTestBase.saveIdentityToCookie):
(WI.AuditTestBase.toJSON):
(WI.AuditTestBase.prototype.determineIfSupported): Added.
(WI.AuditTestBase.prototype.updateSupported): Added.
(WI.AuditTestBase.prototype.updateResult): Added.
(WI.AuditTestBase.prototype.set supported): Deleted.
(WI.AuditTestBase.async setup): Deleted.
(WI.AuditTestBase.prototype.async setup): Deleted.
* UserInterface/Models/AuditTestGroup.js:
(WI.AuditTestGroup):
(WI.AuditTestGroup.prototype.addTest): Added.
(WI.AuditTestGroup.prototype.removeTest): Added.
(WI.AuditTestGroup.prototype.clearResult):
(WI.AuditTestGroup.prototype.async run):
(WI.AuditTestGroup.prototype.determineIfSupported): Added.
(WI.AuditTestGroup.prototype.updateSupported): Added.
(WI.AuditTestGroup.prototype.updateResult): Added.
(WI.AuditTestGroup.prototype._checkDisabled): Added.
(WI.AuditTestGroup.prototype._handleTestCompleted):
(WI.AuditTestGroup.prototype._handleTestDisabledChanged):
(WI.AuditTestGroup.prototype._handleTestSupportedChanged): Added.
(WI.AuditTestGroup.prototype._handleTestChanged): Added.
(WI.AuditTestGroup.prototype.get supported): Deleted.
(WI.AuditTestGroup.prototype.set supported): Deleted.
(WI.AuditTestGroup.prototype.get disabled): Deleted.
(WI.AuditTestGroup.prototype.set disabled): Deleted.
(WI.AuditTestGroup.prototype._updateResult): Deleted.
(WI.AuditTestGroup.prototype._updateResult): Deleted.
* UserInterface/Models/AuditTestCase.js:
(WI.AuditTestCase):
(WI.AuditTestCase.stringifyFunction): Added.
(WI.AuditTestCase.prototype.set test): Added.
(WI.AuditTestCase.prototype.async run):
Allow additional JSON keys to be changed in the UI:
 - `name`
 - `description`
 - `supports` (ensure that changes recalculate whether the audit is actually supported)
 - `setup`
 - (groups) `tests` (via separate add and remove functions)
 - (test cases) the actual `test` function
These properties are expected to only change in edit mode.

* UserInterface/Models/AuditTestResultBase.js:
(WI.AuditTestResultBase.prototype.get disabled): Added.
(WI.AuditTestResultBase.prototype.get editable): Added.
Audit results are never disabled and not editable.

* UserInterface/Controllers/AuditManager.js:
(WI.AuditManager):
(WI.AuditManager.prototype.set editing):
(WI.AuditManager.async start):
(WI.AuditManager.prototype.async start):
(WI.AuditManager.prototype.stop):
(WI.AuditManager.prototype.async processJSON):
(WI.AuditManager.prototype.loadStoredTests):
(WI.AuditManager.prototype.async addTest): Added.
(WI.AuditManager.prototype.removeTest):
(WI.AuditManager.prototype._addDefaultTests):
(WI.AuditManager.prototype._addDefaultTests.removeWhitespace):
(WI.AuditManager.prototype._addTest): Deleted.
(WI.AuditManager.prototype._topLevelTestForTest): Deleted.
Add helper functions and events for when the audit manager running mode changes to update UI.
Also add another default test that demonstrates the `supports` JSON key.

* UserInterface/Views/AuditNavigationSidebarPanel.js:
(WI.AuditNavigationSidebarPanel.prototype.showDefaultContentView):
(WI.AuditNavigationSidebarPanel.prototype.willDismissPopover): Added.
(WI.AuditNavigationSidebarPanel.prototype.initialLayout):
(WI.AuditNavigationSidebarPanel.prototype.matchTreeElementAgainstCustomFilters):
(WI.AuditNavigationSidebarPanel.prototype._addTest):
(WI.AuditNavigationSidebarPanel.prototype._addResult):
(WI.AuditNavigationSidebarPanel.prototype._updateControlNavigationItems): Added.
(WI.AuditNavigationSidebarPanel.prototype._updateEditNavigationItems): Added.
(WI.AuditNavigationSidebarPanel.prototype._handleAuditManagerEditingChanged):
(WI.AuditNavigationSidebarPanel.prototype._handleAuditManagerRunningStateChanged): Added.
(WI.AuditNavigationSidebarPanel.prototype._handleAuditTestAdded):
(WI.AuditNavigationSidebarPanel.prototype._handleAuditTestCompleted):
(WI.AuditNavigationSidebarPanel.prototype._handleAuditTestRemoved):
(WI.AuditNavigationSidebarPanel.prototype._handleAuditTestScheduled):
(WI.AuditNavigationSidebarPanel.prototype._treeSelectionDidChange):
(WI.AuditNavigationSidebarPanel.prototype._handleStartStopButtonNavigationItemClicked):
(WI.AuditNavigationSidebarPanel.prototype._handleCreateButtonNavigationItemClicked): Added.
(WI.AuditNavigationSidebarPanel.prototype._updateStartStopButtonNavigationItemState): Deleted.
(WI.AuditNavigationSidebarPanel.prototype._updateEditButtonNavigationItemState): Deleted.
* UserInterface/Views/AuditNavigationSidebarPanel.css:
(.sidebar > .panel.navigation.audit .edit-audits:not(.disabled):active): Added.
(.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated): Added.
(.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated:active): Added.
(.sidebar > .panel.navigation.audit .edit-audits.disabled): Added.
(.content-view.audit .message-text-view .navigation-item-help:is(.start-editing-audits, .stop-editing-audits) .navigation-bar): Added.
(.content-view.tab.audit .content-view > .audit-version): Added.
(.content-view.tab.audit .content-view .reference-page-link-container): Added.
(body[dir=ltr] .content-view.tab.audit .content-view .reference-page-link-container): Added.
(body[dir=rtl] .content-view.tab.audit .content-view .reference-page-link-container): Added.
(.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled):active): Deleted.
(.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated): Deleted.
(.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated:active): Deleted.
(.sidebar > .panel.navigation.audit > .content .edit-audits.disabled): Deleted.
(.finish-editing-audits-placeholder.message-text-view .navigation-item-help .navigation-bar): Deleted.
(.audit-version): Deleted.
Move the "Edit" toggle to be next to the filter bar so that it's always visible. Replace the
"[|>] Start" button  with a "[+] Create" button when in edit mode, which adds a new audit at
the top-level.

* UserInterface/Views/AuditTestContentView.js:
(WI.AuditTestContentView):
(WI.AuditTestContentView.get navigationItems):
(WI.AuditTestContentView.get supportsSave):
(WI.AuditTestContentView.prototype.createNameElement): Added.
(WI.AuditTestContentView.prototype.createDescriptionElement): Added.
(WI.AuditTestContentView.prototype.createControlsTableElement): Added.
(WI.AuditTestContentView.prototype.layout):
(WI.AuditTestContentView.prototype.shown):
(WI.AuditTestContentView.prototype.hidden):
(WI.AuditTestContentView.prototype.handleResultChanged):
(WI.AuditTestContentView.prototype.showStoppingPlaceholder):
(WI.AuditTestContentView.prototype.showNoResultPlaceholder):
(WI.AuditTestContentView.prototype.showNoResultDataPlaceholder):
(WI.AuditTestContentView.prototype.showFilteredPlaceholder):
(WI.AuditTestContentView.prototype._updateExportNavigationItems): Added.
(WI.AuditTestContentView.prototype._updateSupportsInputState): Added.
(WI.AuditTestContentView.prototype._createSetupEditor): Added.
(WI.AuditTestContentView.prototype._handleEditorKeydown): Added.
(WI.AuditTestContentView.prototype._handleExportTestButtonNavigationItemClicked): Added.
(WI.AuditTestContentView.prototype._handleExportResultButtonNavigationItemClicked): Added.
(WI.AuditTestContentView.prototype._handleTestDisabledChanged): Added.
(WI.AuditTestContentView.prototype._handleTestSupportedChanged): Added.
(WI.AuditTestContentView.prototype._handleEditingChanged): Added.
(WI.AuditTestContentView.prototype._updateExportButtonNavigationItemState): Deleted.
(WI.AuditTestContentView.prototype._handleExportButtonNavigationItemClicked): Deleted.
* UserInterface/Views/AuditTestContentView.css:
(.content-view.audit-test:is(.unsupported, .disabled):not(.manager-editing)): Added.
(.content-view.audit-test.manager-editing .editor:not(:empty)): Added.
(.content-view.audit-test.manager-editing :is(.content-view.audit-test, header) .editor:not(:empty)): Added.
(.content-view.audit-test .CodeMirror): Added.
(.content-view.audit-test > header :is(.name, .description):not([contenteditable])): Added.
(.content-view.audit-test.manager-editing > header :is(.name, .description)[contenteditable]): Added.
(.content-view.audit-test.manager-editing > header .name[contenteditable]:empty): Added.
(.content-view.audit-test.manager-editing > header .name[contenteditable]:empty:before): Added.
(.content-view.audit-test.manager-editing > header .description[contenteditable]:empty:before): Added.
(.content-view.audit-test:not(.manager-editing) > header .description:empty): Added.
(.content-view.audit-test:not(.manager-editing) > header .description:empty, .content-view.audit-test:not(.manager-editing) > header table.controls): Added.
(.content-view.audit-test > header table.controls, .content-view.audit-test > header table.controls > tr > td): Added.
(.content-view.audit-test > header table.controls > tr > th): Added.
(.content-view.audit-test > header table.controls > tr.supports input[type="number"]): Added.
(.content-view.audit-test > header table.controls > tr.supports .warning): Added.
(.content-view.audit-test > header table.controls > tr.supports .warning:not(:empty)::before ): Added.
(.content-view.audit-test > header table.controls > tr.setup .editor): Added.
(.content-view.audit-test > section > .message-text-view > :is(progress, .indeterminate-progress-spinner)): Added.
(@media (prefers-color-scheme: dark) .content-view.audit-test > header table.controls > tr > th): Added.
Create helper functions for subclasses that simplify creating the editing UI. When in edit
mode, add `contenteditable` to the `name`/`description` and inputs for `supports`/`setup`.

* UserInterface/Views/AuditTestGroupContentView.js:
(WI.AuditTestGroupContentView):
(WI.AuditTestGroupContentView.prototype.willDismissPopover): Added.
(WI.AuditTestGroupContentView.prototype.createControlsTableElement): Added.
(WI.AuditTestGroupContentView.prototype.initialLayout):
(WI.AuditTestGroupContentView.prototype.layout):
(WI.AuditTestGroupContentView.prototype.shown):
(WI.AuditTestGroupContentView.prototype.hidden):
(WI.AuditTestGroupContentView.prototype.showRunningPlaceholder):
(WI.AuditTestGroupContentView.prototype._updateClassList): Added.
(WI.AuditTestGroupContentView.prototype._updateLevelScopeBar):
(WI.AuditTestGroupContentView.prototype._addTest): Added.
(WI.AuditTestGroupContentView.prototype._handleTestGroupTestAdded): Added.
(WI.AuditTestGroupContentView.prototype._handleTestGroupTestRemoved): Added.
* UserInterface/Views/AuditTestGroupContentView.css:
(.content-view.audit-test-group > section > .audit-test-group > header): Added.
(.content-view.audit-test-group.contains-test-case > header):
(.content-view.audit-test-group > section > .audit-test-group.contains-test-case > header): Added.
(.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case, .content-view.audit-test-group + .content-view.audit-test-case): Added.
(.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case): Deleted.
When in edit mode, add buttons for removing the audit and adding a new sub-audit (using the
new `WI.CreateAuditPopover` popover).

* UserInterface/Views/AuditTestCaseContentView.js:
(WI.AuditTestCaseContentView):
(WI.AuditTestCaseContentView.prototype.initialLayout):
(WI.AuditTestCaseContentView.prototype.layout):
(WI.AuditTestCaseContentView.prototype.showRunningPlaceholder):
* UserInterface/Views/AuditTestCaseContentView.css:
(.content-view-container > .content-view.audit-test-case): Added.
(.content-view-container > .content-view.audit-test-case > header):
(.content-view-container > .content-view.audit-test-case.manager-editing > header h1 > img): Added.
(.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view, .editor):first-child): Added.
(.content-view-container > .content-view.audit-test-case > section): Added.
(.content-view-container > .content-view.audit-test-case > section, .content-view-container > .content-view.audit-test-case > section :is(.editor, .CodeMirror)): Added.
(.content-view.audit-test-case.manager-editing.disabled:not(.editable) > header h1 > img): Added.
(.content-view.audit-test-case > section > :not(.message-text-view, .editor)): Added.
(.content-view.audit-test-case > section > :not(.message-text-view, .editor):last-child): Added.
(.content-view.audit-test-case > section > :not(.message-text-view, .editor) + :not(.message-text-view, .editor)): Added.
(@media (prefers-color-scheme: dark) .content-view.audit-test-case.manager-editing > header h1 > img): Added.
(.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view):first-child): Deleted.
(.content-view.audit-test-case > section > :not(.message-text-view)): Deleted.
(.content-view.audit-test-case > section > :not(.message-text-view):last-child): Deleted.
(.content-view.audit-test-case > section > :not(.message-text-view) + :not(.message-text-view)): Deleted.
(.content-view.audit-test-case > section .CodeMirror): Deleted.
When in edit mode, replace the icon with a (X) to remove the audit and show a `CodeMirror`
instance to allow editing the content.

* UserInterface/Views/AuditTreeElement.js:
(WI.AuditTreeElement):
(WI.AuditTreeElement.expandedSettingKey): Added.
(WI.AuditTreeElement.prototype.onattach):
(WI.AuditTreeElement.prototype.ondelete):
(WI.AuditTreeElement.prototype.canSelectOnMouseDown): Added.
(WI.AuditTreeElement.prototype.populateContextMenu):
(WI.AuditTreeElement.prototype._handleTestNameChanged): Added.
(WI.AuditTreeElement.prototype._handleTestSupportedChanged): Added.
(WI.AuditTreeElement.prototype._handleTestGroupTestAdded): Added.
* UserInterface/Views/AuditTreeElement.css:
(.tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img): Added.
(body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active).selected:hover > .status > img, body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit.test-case.selected > .status > .indeterminate-progress-spinner, body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit.test-group.selected > .status > progress): Added.
(.tree-outline .item.audit:not(:hover) > .status > img.show-on-hover, .tree-outline .item.audit.test-group.expanded:not(.unsupported, .editing-audits):not(:hover) > .status): Added.
(.tree-outline .item.audit.manager-active > .status > img.show-on-hover, .tree-outline .item.audit.manager-active > .status > img.show-on-hover, .tree-outline .item.audit.test-group.expanded:not(.editing-audits):hover > .status > :not(img), .tree-outline .item.audit.test-group-result.expanded > .status, .tree-outline .item.audit.unsupported + .children .item.audit.unsupported  > .status > img): Added.
(@media (prefers-color-scheme: dark) .tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img): Added.
(.tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active) > .status:hover > img): Deleted.
(.tree-outline .item.audit > .status:not(:hover) > img.show-on-hover, .tree-outline .item.audit.test-group.expanded:not(.unsupported, .editing-audits) > .status:not(:hover)): Deleted.
(.tree-outline .item.audit.manager-active > .status > img.show-on-hover, .tree-outline .item.audit.test-group.expanded:not(.editing-audits) > .status:hover > :not(img), .tree-outline .item.audit.test-group-result.expanded > .status, .tree-outline .item.audit.unsupported + .children .item.audit.unsupported  > .status > img): Deleted.
Add context menu items for duplicating/deleting tests when in edit mode. Adjust the label
and disabled state of existing context menu items for clarity.

* UserInterface/Views/CreateAuditPopover.js: Added.
(WI.CreateAuditPopover):
(WI.CreateAuditPopover.prototype.get audit):
(WI.CreateAuditPopover.prototype.show):
(WI.CreateAuditPopover.prototype.dismiss.const.testFunction):
(WI.CreateAuditPopover.prototype.dismiss):
(WI.CreateAuditPopover.prototype._presentOverTargetElement):
* UserInterface/Views/CreateAuditPopover.css: Added.
(.popover .create-audit-content):
(.popover .create-audit-content > .editor-wrapper):
(.popover .create-audit-content > .editor-wrapper > .reference-page-link):
New popover for creating an audit:

    [<select> of group or test case] [<input> for name] (?)

* UserInterface/Views/Main.css:
(.navigation-item-help > .navigation-bar):
(.message-text-view > .navigation-item-help + .navigation-item-help): Added.
Add styles for when multiple navigation help items are used in the same message text view.

* UserInterface/Views/Variables.css:
(:root):
(@media (prefers-color-scheme: dark) :root):
Add `--filter-invert` to light mode too.

* UserInterface/Base/Utilities.js:
(HTMLInputElement.prototype.autosize):
* UserInterface/Views/CanvasOverviewContentView.js:
(WI.CanvasOverviewContentView):
(WI.CanvasOverviewContentView.prototype._updateRecordingAutoCaptureInputElementSize):
(WI.CanvasOverviewContentView.get recordingAutoCaptureInputMargin): Deleted.
* UserInterface/Views/CanvasOverviewContentView.css:
(.navigation-bar > .item.canvas-recording-auto-capture > label > input):
(.navigation-bar > .item.canvas-recording-auto-capture > label > input::-webkit-inner-spin-button): Deleted.
Create a helper function for autosizing an `<input>`.

* UserInterface/Views/AuditTabContentView.js:
(WI.AuditTabContentView):
(WI.AuditTabContentView.prototype.initialLayout):
Remove the back/foward arrows as they can get into an inconsistent state when editing.
Drive-by: update the drop zone text for clarity.

* UserInterface/Views/SearchSidebarPanel.js:
(WI.SearchSidebarPanel.prototype.showDefaultContentView):
Drive-by: add period to help text.

* UserInterface/Main.html:
* Localizations/en.lproj/localizedStrings.js:

LayoutTests:

* inspector/model/auditTestGroup.html:
* inspector/model/auditTestGroup-expected.txt:
Add tests that check how `disabled` and `supported` (via `supports`) is propagated in groups.

* inspector/audit/run.html:
* inspector/audit/run-expected.txt:
Add tests for `WebInspectorAudit` before `Audit.setup` is called.

* inspector/audit/manager-start-setup.html:
Renaming function call.

* inspector/audit/basic-async.html:
* inspector/audit/basic-boolean.html:
* inspector/audit/basic-debugger.html:
* inspector/audit/basic-error.html:
* inspector/audit/basic-object.html:
* inspector/audit/basic-promise.html:
* inspector/audit/basic-string.html:
* inspector/audit/basic-timeout.html:
* inspector/audit/run-resources.html:
Drive-by: remove `InspectorTest.debug()`.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkLayoutTestsinspectorauditbasicasynchtml">trunk/LayoutTests/inspector/audit/basic-async.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditbasicbooleanhtml">trunk/LayoutTests/inspector/audit/basic-boolean.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditbasicdebuggerhtml">trunk/LayoutTests/inspector/audit/basic-debugger.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditbasicerrorhtml">trunk/LayoutTests/inspector/audit/basic-error.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditbasicobjecthtml">trunk/LayoutTests/inspector/audit/basic-object.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditbasicpromisehtml">trunk/LayoutTests/inspector/audit/basic-promise.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditbasicstringhtml">trunk/LayoutTests/inspector/audit/basic-string.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditbasictimeouthtml">trunk/LayoutTests/inspector/audit/basic-timeout.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditmanagerstartsetuphtml">trunk/LayoutTests/inspector/audit/manager-start-setup.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditrunexpectedtxt">trunk/LayoutTests/inspector/audit/run-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectorauditrunresourceshtml">trunk/LayoutTests/inspector/audit/run-resources.html</a></li>
<li><a href="#trunkLayoutTestsinspectorauditrunhtml">trunk/LayoutTests/inspector/audit/run.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="#trunkSourceWebInspectorUIChangeLog">trunk/Source/WebInspectorUI/ChangeLog</a></li>
<li><a href="#trunkSourceWebInspectorUILocalizationsenlprojlocalizedStringsjs">trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceBaseUtilitiesjs">trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceControllersAuditManagerjs">trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceMainhtml">trunk/Source/WebInspectorUI/UserInterface/Main.html</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceModelsAuditTestBasejs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceModelsAuditTestCasejs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceModelsAuditTestGroupjs">trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.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>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsCanvasOverviewContentViewcss">trunk/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.css</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsCanvasOverviewContentViewjs">trunk/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsMaincss">trunk/Source/WebInspectorUI/UserInterface/Views/Main.css</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsSearchSidebarPaneljs">trunk/Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsVariablescss">trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsCreateAuditPopovercss">trunk/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.css</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsCreateAuditPopoverjs">trunk/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog      2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/ChangeLog 2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -1,3 +1,33 @@
</span><ins>+2020-08-28  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Audit: should be able to create/edit imported audits
+        https://bugs.webkit.org/show_bug.cgi?id=215555
+        <rdar://problem/67255483>
+
+        Reviewed by Devin Rousso.
+
+        * inspector/model/auditTestGroup.html:
+        * inspector/model/auditTestGroup-expected.txt:
+        Add tests that check how `disabled` and `supported` (via `supports`) is propagated in groups.
+
+        * inspector/audit/run.html:
+        * inspector/audit/run-expected.txt:
+        Add tests for `WebInspectorAudit` before `Audit.setup` is called.
+
+        * inspector/audit/manager-start-setup.html:
+        Renaming function call.
+
+        * inspector/audit/basic-async.html:
+        * inspector/audit/basic-boolean.html:
+        * inspector/audit/basic-debugger.html:
+        * inspector/audit/basic-error.html:
+        * inspector/audit/basic-object.html:
+        * inspector/audit/basic-promise.html:
+        * inspector/audit/basic-string.html:
+        * inspector/audit/basic-timeout.html:
+        * inspector/audit/run-resources.html:
+        Drive-by: remove `InspectorTest.debug()`.
+
</ins><span class="cx"> 2020-08-28  Hector Lopez  <hector_i_lopez@apple.com>
</span><span class="cx"> 
</span><span class="cx">         [ macOS ] Tests expectations changed as test passing but expected to fail
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditbasicasynchtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/basic-async.html (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/basic-async.html       2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/audit/basic-async.html  2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -9,8 +9,6 @@
</span><span class="cx"> 
</span><span class="cx"> function test()
</span><span class="cx"> {
</span><del>-    InspectorTest.debug();
-
</del><span class="cx">     let suite = InspectorTest.Audit.createSuite("Audit.Basic");
</span><span class="cx"> 
</span><span class="cx">     InspectorTest.Audit.addFunctionlessTest("Audit.Basic.Async.Boolean.True", true, WI.AuditTestCaseResult.Level.Pass, {async: true});
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditbasicbooleanhtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/basic-boolean.html (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/basic-boolean.html     2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/audit/basic-boolean.html        2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -9,8 +9,6 @@
</span><span class="cx"> 
</span><span class="cx"> function test()
</span><span class="cx"> {
</span><del>-    InspectorTest.debug();
-
</del><span class="cx">     let suite = InspectorTest.Audit.createSuite("Audit.Basic");
</span><span class="cx"> 
</span><span class="cx">     InspectorTest.Audit.addFunctionlessTest("Audit.Basic.Boolean.True", true, WI.AuditTestCaseResult.Level.Pass);
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditbasicdebuggerhtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/basic-debugger.html (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/basic-debugger.html    2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/audit/basic-debugger.html       2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -9,8 +9,6 @@
</span><span class="cx"> 
</span><span class="cx"> function test()
</span><span class="cx"> {
</span><del>-    InspectorTest.debug();
-
</del><span class="cx">     let suite = InspectorTest.Audit.createSuite("Audit.Basic");
</span><span class="cx"> 
</span><span class="cx">     suite.addTestCase({
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditbasicerrorhtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/basic-error.html (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/basic-error.html       2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/audit/basic-error.html  2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -9,8 +9,6 @@
</span><span class="cx"> 
</span><span class="cx"> function test()
</span><span class="cx"> {
</span><del>-    InspectorTest.debug();
-
</del><span class="cx">     let suite = InspectorTest.Audit.createSuite("Audit.Basic");
</span><span class="cx"> 
</span><span class="cx">     InspectorTest.Audit.addFunctionlessTest("Audit.Basic.Error.Undefined", undefined, WI.AuditTestCaseResult.Level.Error);
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditbasicobjecthtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/basic-object.html (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/basic-object.html      2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/audit/basic-object.html 2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -9,8 +9,6 @@
</span><span class="cx"> 
</span><span class="cx"> function test()
</span><span class="cx"> {
</span><del>-    InspectorTest.debug();
-
</del><span class="cx">     let suite = InspectorTest.Audit.createSuite("Audit.Basic");
</span><span class="cx"> 
</span><span class="cx">     InspectorTest.Audit.addObjectTest("Audit.Basic.Object.Pass", {level: WI.AuditTestCaseResult.Level.Pass}, WI.AuditTestCaseResult.Level.Pass);
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditbasicpromisehtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/basic-promise.html (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/basic-promise.html     2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/audit/basic-promise.html        2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -9,8 +9,6 @@
</span><span class="cx"> 
</span><span class="cx"> function test()
</span><span class="cx"> {
</span><del>-    InspectorTest.debug();
-
</del><span class="cx">     let suite = InspectorTest.Audit.createSuite("Audit.Basic");
</span><span class="cx"> 
</span><span class="cx">     InspectorTest.Audit.addPromiseTest("Audit.Basic.Promise.Boolean.True", `resolve(true)`, WI.AuditTestCaseResult.Level.Pass);
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditbasicstringhtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/basic-string.html (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/basic-string.html      2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/audit/basic-string.html 2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -9,8 +9,6 @@
</span><span class="cx"> 
</span><span class="cx"> function test()
</span><span class="cx"> {
</span><del>-    InspectorTest.debug();
-
</del><span class="cx">     let suite = InspectorTest.Audit.createSuite("Audit.Basic");
</span><span class="cx"> 
</span><span class="cx">     InspectorTest.Audit.addStringTest("Audit.Basic.String.Pass", WI.AuditTestCaseResult.Level.Pass, WI.AuditTestCaseResult.Level.Pass);
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditbasictimeouthtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/basic-timeout.html (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/basic-timeout.html     2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/audit/basic-timeout.html        2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -9,8 +9,6 @@
</span><span class="cx"> 
</span><span class="cx"> function test()
</span><span class="cx"> {
</span><del>-    InspectorTest.debug();
-
</del><span class="cx">     let suite = InspectorTest.Audit.createSuite("Audit.Basic");
</span><span class="cx"> 
</span><span class="cx">     InspectorTest.Audit.addPromiseTest("Audit.Basic.Timeout.Pass", `setTimeout(resolve, 0, "${WI.AuditTestCaseResult.Level.Pass}")`, WI.AuditTestCaseResult.Level.Pass);
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditmanagerstartsetuphtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/manager-start-setup.html (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/manager-start-setup.html       2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/audit/manager-start-setup.html  2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -14,7 +14,7 @@
</span><span class="cx"> }).toString();
</span><span class="cx"> 
</span><span class="cx">     async function wrapTest(audit, expected, {getResultCallback} = {}) {
</span><del>-        WI.auditManager._addTest(audit);
</del><ins>+        WI.auditManager.addTest(audit);
</ins><span class="cx"> 
</span><span class="cx">         let [result] = await WI.auditManager.start([audit]);
</span><span class="cx">         if (getResultCallback)
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditrunexpectedtxt"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/run-expected.txt (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/run-expected.txt       2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/audit/run-expected.txt  2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -6,7 +6,11 @@
</span><span class="cx"> Audit run "function() { return 42; }"...
</span><span class="cx"> PASS: Run should send back 42.
</span><span class="cx"> 
</span><del>--- Running test case: Audit.run.Valid.InjectedObject
</del><ins>+-- Running test case: Audit.run.Valid.InjectedObject.BeforeSetup
+Audit run "function() { return WebInspectorAudit; }"...
+PASS: The injected WebInspectorAudit should be undefined.
+
+-- Running test case: Audit.run.Valid.InjectedObject.AfterSetup
</ins><span class="cx"> Audit setup...
</span><span class="cx"> Audit run "function() { return WebInspectorAudit; }"...
</span><span class="cx"> PASS: The injected WebInspectorAudit should be an object.
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditrunresourceshtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/run-resources.html (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/run-resources.html     2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/audit/run-resources.html        2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -9,8 +9,6 @@
</span><span class="cx"> <script>
</span><span class="cx"> function test()
</span><span class="cx"> {
</span><del>-    InspectorTest.debug();
-
</del><span class="cx">     let suite = InspectorTest.Audit.createSuite("Audit.run.Resources");
</span><span class="cx"> 
</span><span class="cx">     function evaluateStringForTest(func, args) {
</span></span></pre></div>
<a id="trunkLayoutTestsinspectorauditrunhtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/audit/run.html (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/audit/run.html       2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/audit/run.html  2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -31,7 +31,17 @@
</span><span class="cx">     });
</span><span class="cx"> 
</span><span class="cx">     suite.addTestCase({
</span><del>-        name: "Audit.run.Valid.InjectedObject",
</del><ins>+        name: "Audit.run.Valid.InjectedObject.BeforeSetup",
+        description: "Check that an error is not thrown if trying to reference the injected object without calling setup.",
+        async test() {
+            await auditRun(`function() { return WebInspectorAudit; }`, (result) => {
+                InspectorTest.expectEqual(result.type, "undefined", "The injected WebInspectorAudit should be undefined.");
+            });
+        },
+    });
+
+    suite.addTestCase({
+        name: "Audit.run.Valid.InjectedObject.AfterSetup",
</ins><span class="cx">         description: "Check that an object is injected into the function that is executed.",
</span><span class="cx">         async test() {
</span><span class="cx">             await InspectorTest.Audit.setupAudit();
</span></span></pre></div>
<a id="trunkLayoutTestsinspectormodelauditTestGroupexpectedtxt"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt    2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt       2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -109,3 +109,180 @@
</span><span class="cx">   ]
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+-- Running test case: AuditTestGroup.Disabled.Constructor.Enabled
+PASS: Group should not be disabled.
+PASS: Test1 should not be disabled.
+PASS: Test2 should not be disabled.
+
+-- Running test case: AuditTestGroup.Disabled.Constructor.Disabled
+PASS: Group should be disabled.
+PASS: Test1 should be disabled.
+PASS: Test2 should be disabled.
+
+-- Running test case: AuditTestGroup.Disabled.ChangeGroup.Enabled
+PASS: Group should not be disabled.
+PASS: Test1 should not be disabled.
+PASS: Test2 should not be disabled.
+
+Marking group as disabled...
+PASS: Group should be disabled.
+PASS: Test1 should be disabled.
+PASS: Test2 should be disabled.
+
+-- Running test case: AuditTestGroup.Disabled.ChangeGroup.DisabledGroup
+PASS: Group should be disabled.
+PASS: Test1 should be disabled.
+PASS: Test2 should be disabled.
+
+Marking group as enabled...
+PASS: Group should not be disabled.
+PASS: Test1 should not be disabled.
+PASS: Test2 should not be disabled.
+
+-- Running test case: AuditTestGroup.Disabled.ChangeGroup.DisabledTests
+PASS: Group should be disabled.
+PASS: Test1 should be disabled.
+PASS: Test2 should be disabled.
+
+Marking group as enabled...
+PASS: Group should not be disabled.
+PASS: Test1 should not be disabled.
+PASS: Test2 should not be disabled.
+
+-- Running test case: AuditTestGroup.Disabled.ChangeTests.Enabled
+PASS: Group should not be disabled.
+PASS: Test1 should not be disabled.
+PASS: Test2 should not be disabled.
+
+Marking Test1 as disabled...
+PASS: Group should not be disabled.
+PASS: Test1 should be disabled.
+PASS: Test2 should not be disabled.
+
+Marking Test2 as disabled...
+PASS: Group should be disabled.
+PASS: Test1 should be disabled.
+PASS: Test2 should be disabled.
+
+-- Running test case: AuditTestGroup.Disabled.ChangeTests.DisabledGroup
+PASS: Group should be disabled.
+PASS: Test1 should be disabled.
+PASS: Test2 should be disabled.
+
+Marking test as enabled 1...
+PASS: Group should not be disabled.
+PASS: Test1 should not be disabled.
+PASS: Test2 should be disabled.
+
+Marking test as enabled 2...
+PASS: Group should not be disabled.
+PASS: Test1 should not be disabled.
+PASS: Test2 should not be disabled.
+
+-- Running test case: AuditTestGroup.Disabled.ChangeTests.DisabledTests
+PASS: Group should be disabled.
+PASS: Test1 should be disabled.
+PASS: Test2 should be disabled.
+
+Marking test as enabled 1...
+PASS: Group should not be disabled.
+PASS: Test1 should not be disabled.
+PASS: Test2 should be disabled.
+
+Marking test as enabled 2...
+PASS: Group should not be disabled.
+PASS: Test1 should not be disabled.
+PASS: Test2 should not be disabled.
+
+-- Running test case: AuditTestGroup.Supports.Constructor.Supported
+PASS: Group should be supported.
+PASS: Test1 should be supported.
+PASS: Test2 should be supported.
+
+-- Running test case: AuditTestGroup.Supports.Constructor.Unsupported
+WARN: Audit Warning: "Group" is too new to run in this Web Inspector
+PASS: Group should not be supported.
+PASS: Test1 should not be supported.
+PASS: Test2 should not be supported.
+
+-- Running test case: AuditTestGroup.Supports.ChangeGroup.Supported
+PASS: Group should be supported.
+PASS: Test1 should be supported.
+PASS: Test2 should be supported.
+
+Marking group as unsupported...
+PASS: Group should not be supported.
+PASS: Test1 should not be supported.
+PASS: Test2 should not be supported.
+
+-- Running test case: AuditTestGroup.Supports.ChangeGroup.UnsupportedGroup
+WARN: Audit Warning: "Group" is too new to run in this Web Inspector
+PASS: Group should not be supported.
+PASS: Test1 should not be supported.
+PASS: Test2 should not be supported.
+
+Marking group as supported...
+PASS: Group should be supported.
+PASS: Test1 should be supported.
+PASS: Test2 should be supported.
+
+-- Running test case: AuditTestGroup.Supports.ChangeGroup.UnsupportedTests
+WARN: Audit Warning: "Test1" is too new to run in this Web Inspector
+WARN: Audit Warning: "Test2" is too new to run in this Web Inspector
+PASS: Group should not be supported.
+PASS: Test1 should not be supported.
+PASS: Test2 should not be supported.
+
+Marking group as supported...
+PASS: Group should not be supported.
+PASS: Test1 should not be supported.
+PASS: Test2 should not be supported.
+
+-- Running test case: AuditTestGroup.Supports.ChangeTests.Supported
+PASS: Group should be supported.
+PASS: Test1 should be supported.
+PASS: Test2 should be supported.
+
+Marking Test1 as unsupported...
+PASS: Group should be supported.
+PASS: Test1 should not be supported.
+PASS: Test2 should be supported.
+
+Marking Test2 as unsupported...
+PASS: Group should not be supported.
+PASS: Test1 should not be supported.
+PASS: Test2 should not be supported.
+
+-- Running test case: AuditTestGroup.Supports.ChangeTests.UnsupportedGroup
+WARN: Audit Warning: "Group" is too new to run in this Web Inspector
+PASS: Group should not be supported.
+PASS: Test1 should not be supported.
+PASS: Test2 should not be supported.
+
+Marking Test1 as supported...
+PASS: Group should not be supported.
+PASS: Test1 should not be supported.
+PASS: Test2 should not be supported.
+
+Marking Test2 as supported...
+PASS: Group should not be supported.
+PASS: Test1 should not be supported.
+PASS: Test2 should not be supported.
+
+-- Running test case: AuditTestGroup.Supports.ChangeTests.UnsupportedTests
+WARN: Audit Warning: "Test1" is too new to run in this Web Inspector
+WARN: Audit Warning: "Test2" is too new to run in this Web Inspector
+PASS: Group should not be supported.
+PASS: Test1 should not be supported.
+PASS: Test2 should not be supported.
+
+Marking Test1 as supported...
+PASS: Group should be supported.
+PASS: Test1 should be supported.
+PASS: Test2 should not be supported.
+
+Marking Test2 as supported...
+PASS: Group should be supported.
+PASS: Test1 should be supported.
+PASS: Test2 should be supported.
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectormodelauditTestGrouphtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/model/auditTestGroup.html (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/model/auditTestGroup.html    2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/LayoutTests/inspector/model/auditTestGroup.html       2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -150,6 +150,390 @@
</span><span class="cx">     ];
</span><span class="cx">     payloadTests.forEach(addPayloadTest);
</span><span class="cx"> 
</span><ins>+    suite.addTestCase({
+        name: "AuditTestGroup.Disabled.Constructor.Enabled",
+        description: "Check that `disabled` constructor option properly propagates.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}"),
+                new WI.AuditTestCase("Test2", "function() {}"),
+            ]);
+
+            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
+            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
+            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Disabled.Constructor.Disabled",
+        description: "Check that `disabled` constructor option properly propagates.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}"),
+                new WI.AuditTestCase("Test2", "function() {}"),
+            ], {disabled: true});
+
+            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
+            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
+            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Disabled.ChangeGroup.Enabled",
+        description: "Check that `disabled` is propagated to tests when set on group.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}"),
+                new WI.AuditTestCase("Test2", "function() {}"),
+            ]);
+
+            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
+            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
+            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking group as disabled...");
+
+            group.disabled = true;
+            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
+            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
+            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Disabled.ChangeGroup.DisabledGroup",
+        description: "Check that `disabled` is propagated to tests when set on group.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}"),
+                new WI.AuditTestCase("Test2", "function() {}"),
+            ], {disabled: true});
+
+            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
+            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
+            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking group as enabled...");
+
+            group.disabled = false;
+            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
+            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
+            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Disabled.ChangeGroup.DisabledTests",
+        description: "Check that `disabled` is propagated to tests when set on group.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}", {disabled: true}),
+                new WI.AuditTestCase("Test2", "function() {}", {disabled: true}),
+            ]);
+
+            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
+            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
+            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking group as enabled...");
+
+            group.disabled = false;
+            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
+            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
+            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Disabled.ChangeTests.Enabled",
+        description: "Check that `disabled` is propagated to group when set on tests.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}"),
+                new WI.AuditTestCase("Test2", "function() {}"),
+            ]);
+
+            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
+            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
+            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking Test1 as disabled...");
+
+            group.tests[0].disabled = true;
+            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
+            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
+            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking Test2 as disabled...");
+
+            group.tests[1].disabled = true;
+            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
+            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
+            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Disabled.ChangeTests.DisabledGroup",
+        description: "Check that `disabled` is propagated to group when set on tests.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}"),
+                new WI.AuditTestCase("Test2", "function() {}"),
+            ], {disabled: true});
+
+            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
+            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
+            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking test as enabled 1...");
+
+            group.tests[0].disabled = false;
+            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
+            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
+            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking test as enabled 2...");
+
+            group.tests[1].disabled = false;
+            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
+            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
+            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Disabled.ChangeTests.DisabledTests",
+        description: "Check that `disabled` is propagated to group when set on tests.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}", {disabled: true}),
+                new WI.AuditTestCase("Test2", "function() {}", {disabled: true}),
+            ]);
+
+            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
+            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
+            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking test as enabled 1...");
+
+            group.tests[0].disabled = false;
+            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
+            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
+            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking test as enabled 2...");
+
+            group.tests[1].disabled = false;
+            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
+            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
+            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Supports.Constructor.Supported",
+        description: "Check that `supported` properly propagates with `supports` constructor option.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}"),
+                new WI.AuditTestCase("Test2", "function() {}"),
+            ]);
+
+            InspectorTest.expectTrue(group.supported, "Group should be supported.");
+            InspectorTest.expectTrue(group.tests[0].supported, "Test1 should be supported.");
+            InspectorTest.expectTrue(group.tests[1].supported, "Test2 should be supported.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Supports.Constructor.Unsupported",
+        description: "Check that `supported` properly propagates with `supports` constructor option.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}"),
+                new WI.AuditTestCase("Test2", "function() {}"),
+            ], {supports: WI.AuditTestBase.Version + 1});
+
+            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
+            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
+            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Supports.ChangeGroup.Supported",
+        description: "Check that `supported` properly propagates to tests when `supports` is set on group.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}"),
+                new WI.AuditTestCase("Test2", "function() {}"),
+            ]);
+
+            InspectorTest.expectTrue(group.supported, "Group should be supported.");
+            InspectorTest.expectTrue(group.tests[0].supported, "Test1 should be supported.");
+            InspectorTest.expectTrue(group.tests[1].supported, "Test2 should be supported.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking group as unsupported...");
+
+            group.supports = WI.AuditTestBase.Version + 1;
+            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
+            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
+            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Supports.ChangeGroup.UnsupportedGroup",
+        description: "Check that `supported` properly propagates to tests when `supports` is set on group.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}"),
+                new WI.AuditTestCase("Test2", "function() {}"),
+            ], {supports: WI.AuditTestBase.Version + 1});
+
+            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
+            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
+            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking group as supported...");
+
+            group.supports = NaN;
+            InspectorTest.expectTrue(group.supported, "Group should be supported.");
+            InspectorTest.expectTrue(group.tests[0].supported, "Test1 should be supported.");
+            InspectorTest.expectTrue(group.tests[1].supported, "Test2 should be supported.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Supports.ChangeGroup.UnsupportedTests",
+        description: "Check that `supported` properly propagates to tests when `supports` is set on group.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}", {supports: WI.AuditTestBase.Version + 1}),
+                new WI.AuditTestCase("Test2", "function() {}", {supports: WI.AuditTestBase.Version + 1}),
+            ]);
+
+            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
+            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
+            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking group as supported...");
+
+            group.supports = NaN;
+            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
+            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
+            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Supports.ChangeTests.Supported",
+        description: "Check that `supported` properly propagates to group when `supports` is set on tests.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}"),
+                new WI.AuditTestCase("Test2", "function() {}"),
+            ]);
+
+            InspectorTest.expectTrue(group.supported, "Group should be supported.");
+            InspectorTest.expectTrue(group.tests[0].supported, "Test1 should be supported.");
+            InspectorTest.expectTrue(group.tests[1].supported, "Test2 should be supported.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking Test1 as unsupported...");
+
+            group.tests[0].supports = WI.AuditTestBase.Version + 1;
+            InspectorTest.expectTrue(group.supported, "Group should be supported.");
+            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
+            InspectorTest.expectTrue(group.tests[1].supported, "Test2 should be supported.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking Test2 as unsupported...");
+
+            group.tests[1].supports = WI.AuditTestBase.Version + 1;
+            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
+            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
+            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Supports.ChangeTests.UnsupportedGroup",
+        description: "Check that `supported` properly propagates to group when `supports` is set on tests.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}"),
+                new WI.AuditTestCase("Test2", "function() {}"),
+            ], {supports: WI.AuditTestBase.Version + 1});
+
+            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
+            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
+            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking Test1 as supported...");
+
+            group.tests[0].supports = NaN;
+            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
+            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
+            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking Test2 as supported...");
+
+            group.tests[1].supports = NaN;
+            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
+            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
+            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
+        },
+    });
+
+    suite.addTestCase({
+        name: "AuditTestGroup.Supports.ChangeTests.UnsupportedTests",
+        description: "Check that `supported` properly propagates to group when `supports` is set on tests.",
+        async test() {
+            let group = new WI.AuditTestGroup("Group", [
+                new WI.AuditTestCase("Test1", "function() {}", {supports: WI.AuditTestBase.Version + 1}),
+                new WI.AuditTestCase("Test2", "function() {}", {supports: WI.AuditTestBase.Version + 1}),
+            ]);
+
+            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
+            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
+            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking Test1 as supported...");
+
+            group.tests[0].supports = NaN;
+            InspectorTest.expectTrue(group.supported, "Group should be supported.");
+            InspectorTest.expectTrue(group.tests[0].supported, "Test1 should be supported.");
+            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
+
+            InspectorTest.newline();
+            InspectorTest.log("Marking Test2 as supported...");
+
+            group.tests[1].supports = NaN;
+            InspectorTest.expectTrue(group.supported, "Group should be supported.");
+            InspectorTest.expectTrue(group.tests[0].supported, "Test1 should be supported.");
+            InspectorTest.expectTrue(group.tests[1].supported, "Test2 should be supported.");
+        },
+    });
+
</ins><span class="cx">     suite.runTestCasesAndFinish();
</span><span class="cx"> }
</span><span class="cx"> </script>
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/ChangeLog (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/ChangeLog    2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/ChangeLog       2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -1,3 +1,305 @@
</span><ins>+2020-08-28  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Audit: should be able to create/edit imported audits
+        https://bugs.webkit.org/show_bug.cgi?id=215555
+        <rdar://problem/67255483>
+
+        Reviewed by Devin Rousso.
+
+        Extend the existing audit edit mode (which previously only let audits be disabled/deleted)
+        to also allow other JSON properties of audits to be added/edited/removed, meaning that
+        audits can now be completely created/configured from within the Web Inspector UI. \(o.o)/
+
+        * UserInterface/Models/AuditTestBase.js:
+        (WI.AuditTestBase):
+        (WI.AuditTestBase.prototype.set name): Added.
+        (WI.AuditTestBase.prototype.set description): Added.
+        (WI.AuditTestBase.prototype.get supports): Added.
+        (WI.AuditTestBase.prototype.set supports): Added.
+        (WI.AuditTestBase.prototype.get setup): Added.
+        (WI.AuditTestBase.prototype.set setup): Added.
+        (WI.AuditTestBase.prototype.set disabled):
+        (WI.AuditTestBase.prototype.get editable): Added.
+        (WI.AuditTestBase.prototype.get default): Added.
+        (WI.AuditTestBase.prototype.markAsDefault): Added.
+        (WI.AuditTestBase.prototype.get topLevelTest): Added.
+        (WI.AuditTestBase.prototype.async runSetup): Added.
+        (WI.AuditTestBase.prototype.async start()):
+        (WI.AuditTestBase.prototype.stop()):
+        (WI.AuditTestBase.prototype.async clone): Added.
+        (WI.AuditTestBase.remove): Added.
+        (WI.AuditTestBase.saveIdentityToCookie):
+        (WI.AuditTestBase.toJSON):
+        (WI.AuditTestBase.prototype.determineIfSupported): Added.
+        (WI.AuditTestBase.prototype.updateSupported): Added.
+        (WI.AuditTestBase.prototype.updateResult): Added.
+        (WI.AuditTestBase.prototype.set supported): Deleted.
+        (WI.AuditTestBase.async setup): Deleted.
+        (WI.AuditTestBase.prototype.async setup): Deleted.
+        * UserInterface/Models/AuditTestGroup.js:
+        (WI.AuditTestGroup):
+        (WI.AuditTestGroup.prototype.addTest): Added.
+        (WI.AuditTestGroup.prototype.removeTest): Added.
+        (WI.AuditTestGroup.prototype.clearResult):
+        (WI.AuditTestGroup.prototype.async run):
+        (WI.AuditTestGroup.prototype.determineIfSupported): Added.
+        (WI.AuditTestGroup.prototype.updateSupported): Added.
+        (WI.AuditTestGroup.prototype.updateResult): Added.
+        (WI.AuditTestGroup.prototype._checkDisabled): Added.
+        (WI.AuditTestGroup.prototype._handleTestCompleted):
+        (WI.AuditTestGroup.prototype._handleTestDisabledChanged):
+        (WI.AuditTestGroup.prototype._handleTestSupportedChanged): Added.
+        (WI.AuditTestGroup.prototype._handleTestChanged): Added.
+        (WI.AuditTestGroup.prototype.get supported): Deleted.
+        (WI.AuditTestGroup.prototype.set supported): Deleted.
+        (WI.AuditTestGroup.prototype.get disabled): Deleted.
+        (WI.AuditTestGroup.prototype.set disabled): Deleted.
+        (WI.AuditTestGroup.prototype._updateResult): Deleted.
+        (WI.AuditTestGroup.prototype._updateResult): Deleted.
+        * UserInterface/Models/AuditTestCase.js:
+        (WI.AuditTestCase):
+        (WI.AuditTestCase.stringifyFunction): Added.
+        (WI.AuditTestCase.prototype.set test): Added.
+        (WI.AuditTestCase.prototype.async run):
+        Allow additional JSON keys to be changed in the UI:
+         - `name`
+         - `description`
+         - `supports` (ensure that changes recalculate whether the audit is actually supported)
+         - `setup`
+         - (groups) `tests` (via separate add and remove functions)
+         - (test cases) the actual `test` function
+        These properties are expected to only change in edit mode.
+
+        * UserInterface/Models/AuditTestResultBase.js:
+        (WI.AuditTestResultBase.prototype.get disabled): Added.
+        (WI.AuditTestResultBase.prototype.get editable): Added.
+        Audit results are never disabled and not editable.
+
+        * UserInterface/Controllers/AuditManager.js:
+        (WI.AuditManager):
+        (WI.AuditManager.prototype.set editing):
+        (WI.AuditManager.async start):
+        (WI.AuditManager.prototype.async start):
+        (WI.AuditManager.prototype.stop):
+        (WI.AuditManager.prototype.async processJSON):
+        (WI.AuditManager.prototype.loadStoredTests):
+        (WI.AuditManager.prototype.async addTest): Added.
+        (WI.AuditManager.prototype.removeTest):
+        (WI.AuditManager.prototype._addDefaultTests):
+        (WI.AuditManager.prototype._addDefaultTests.removeWhitespace):
+        (WI.AuditManager.prototype._addTest): Deleted.
+        (WI.AuditManager.prototype._topLevelTestForTest): Deleted.
+        Add helper functions and events for when the audit manager running mode changes to update UI.
+        Also add another default test that demonstrates the `supports` JSON key.
+
+        * UserInterface/Views/AuditNavigationSidebarPanel.js:
+        (WI.AuditNavigationSidebarPanel.prototype.showDefaultContentView):
+        (WI.AuditNavigationSidebarPanel.prototype.willDismissPopover): Added.
+        (WI.AuditNavigationSidebarPanel.prototype.initialLayout):
+        (WI.AuditNavigationSidebarPanel.prototype.matchTreeElementAgainstCustomFilters):
+        (WI.AuditNavigationSidebarPanel.prototype._addTest):
+        (WI.AuditNavigationSidebarPanel.prototype._addResult):
+        (WI.AuditNavigationSidebarPanel.prototype._updateControlNavigationItems): Added.
+        (WI.AuditNavigationSidebarPanel.prototype._updateEditNavigationItems): Added.
+        (WI.AuditNavigationSidebarPanel.prototype._handleAuditManagerEditingChanged):
+        (WI.AuditNavigationSidebarPanel.prototype._handleAuditManagerRunningStateChanged): Added.
+        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestAdded):
+        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestCompleted):
+        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestRemoved):
+        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestScheduled):
+        (WI.AuditNavigationSidebarPanel.prototype._treeSelectionDidChange):
+        (WI.AuditNavigationSidebarPanel.prototype._handleStartStopButtonNavigationItemClicked):
+        (WI.AuditNavigationSidebarPanel.prototype._handleCreateButtonNavigationItemClicked): Added.
+        (WI.AuditNavigationSidebarPanel.prototype._updateStartStopButtonNavigationItemState): Deleted.
+        (WI.AuditNavigationSidebarPanel.prototype._updateEditButtonNavigationItemState): Deleted.
+        * UserInterface/Views/AuditNavigationSidebarPanel.css:
+        (.sidebar > .panel.navigation.audit .edit-audits:not(.disabled):active): Added.
+        (.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated): Added.
+        (.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated:active): Added.
+        (.sidebar > .panel.navigation.audit .edit-audits.disabled): Added.
+        (.content-view.audit .message-text-view .navigation-item-help:is(.start-editing-audits, .stop-editing-audits) .navigation-bar): Added.
+        (.content-view.tab.audit .content-view > .audit-version): Added.
+        (.content-view.tab.audit .content-view .reference-page-link-container): Added.
+        (body[dir=ltr] .content-view.tab.audit .content-view .reference-page-link-container): Added.
+        (body[dir=rtl] .content-view.tab.audit .content-view .reference-page-link-container): Added.
+        (.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled):active): Deleted.
+        (.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated): Deleted.
+        (.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated:active): Deleted.
+        (.sidebar > .panel.navigation.audit > .content .edit-audits.disabled): Deleted.
+        (.finish-editing-audits-placeholder.message-text-view .navigation-item-help .navigation-bar): Deleted.
+        (.audit-version): Deleted.
+        Move the "Edit" toggle to be next to the filter bar so that it's always visible. Replace the
+        "[|>] Start" button  with a "[+] Create" button when in edit mode, which adds a new audit at
+        the top-level.
+
+        * UserInterface/Views/AuditTestContentView.js:
+        (WI.AuditTestContentView):
+        (WI.AuditTestContentView.get navigationItems):
+        (WI.AuditTestContentView.get supportsSave):
+        (WI.AuditTestContentView.prototype.createNameElement): Added.
+        (WI.AuditTestContentView.prototype.createDescriptionElement): Added.
+        (WI.AuditTestContentView.prototype.createControlsTableElement): Added.
+        (WI.AuditTestContentView.prototype.layout):
+        (WI.AuditTestContentView.prototype.shown):
+        (WI.AuditTestContentView.prototype.hidden):
+        (WI.AuditTestContentView.prototype.handleResultChanged):
+        (WI.AuditTestContentView.prototype.showStoppingPlaceholder):
+        (WI.AuditTestContentView.prototype.showNoResultPlaceholder):
+        (WI.AuditTestContentView.prototype.showNoResultDataPlaceholder):
+        (WI.AuditTestContentView.prototype.showFilteredPlaceholder):
+        (WI.AuditTestContentView.prototype._updateExportNavigationItems): Added.
+        (WI.AuditTestContentView.prototype._updateSupportsInputState): Added.
+        (WI.AuditTestContentView.prototype._createSetupEditor): Added.
+        (WI.AuditTestContentView.prototype._handleEditorKeydown): Added.
+        (WI.AuditTestContentView.prototype._handleExportTestButtonNavigationItemClicked): Added.
+        (WI.AuditTestContentView.prototype._handleExportResultButtonNavigationItemClicked): Added.
+        (WI.AuditTestContentView.prototype._handleTestDisabledChanged): Added.
+        (WI.AuditTestContentView.prototype._handleTestSupportedChanged): Added.
+        (WI.AuditTestContentView.prototype._handleEditingChanged): Added.
+        (WI.AuditTestContentView.prototype._updateExportButtonNavigationItemState): Deleted.
+        (WI.AuditTestContentView.prototype._handleExportButtonNavigationItemClicked): Deleted.
+        * UserInterface/Views/AuditTestContentView.css:
+        (.content-view.audit-test:is(.unsupported, .disabled):not(.manager-editing)): Added.
+        (.content-view.audit-test.manager-editing .editor:not(:empty)): Added.
+        (.content-view.audit-test.manager-editing :is(.content-view.audit-test, header) .editor:not(:empty)): Added.
+        (.content-view.audit-test .CodeMirror): Added.
+        (.content-view.audit-test > header :is(.name, .description):not([contenteditable])): Added.
+        (.content-view.audit-test.manager-editing > header :is(.name, .description)[contenteditable]): Added.
+        (.content-view.audit-test.manager-editing > header .name[contenteditable]:empty): Added.
+        (.content-view.audit-test.manager-editing > header .name[contenteditable]:empty:before): Added.
+        (.content-view.audit-test.manager-editing > header .description[contenteditable]:empty:before): Added.
+        (.content-view.audit-test:not(.manager-editing) > header .description:empty): Added.
+        (.content-view.audit-test:not(.manager-editing) > header .description:empty, .content-view.audit-test:not(.manager-editing) > header table.controls): Added.
+        (.content-view.audit-test > header table.controls, .content-view.audit-test > header table.controls > tr > td): Added.
+        (.content-view.audit-test > header table.controls > tr > th): Added.
+        (.content-view.audit-test > header table.controls > tr.supports input[type="number"]): Added.
+        (.content-view.audit-test > header table.controls > tr.supports .warning): Added.
+        (.content-view.audit-test > header table.controls > tr.supports .warning:not(:empty)::before ): Added.
+        (.content-view.audit-test > header table.controls > tr.setup .editor): Added.
+        (.content-view.audit-test > section > .message-text-view > :is(progress, .indeterminate-progress-spinner)): Added.
+        (@media (prefers-color-scheme: dark) .content-view.audit-test > header table.controls > tr > th): Added.
+        Create helper functions for subclasses that simplify creating the editing UI. When in edit
+        mode, add `contenteditable` to the `name`/`description` and inputs for `supports`/`setup`.
+
+        * UserInterface/Views/AuditTestGroupContentView.js:
+        (WI.AuditTestGroupContentView):
+        (WI.AuditTestGroupContentView.prototype.willDismissPopover): Added.
+        (WI.AuditTestGroupContentView.prototype.createControlsTableElement): Added.
+        (WI.AuditTestGroupContentView.prototype.initialLayout):
+        (WI.AuditTestGroupContentView.prototype.layout):
+        (WI.AuditTestGroupContentView.prototype.shown):
+        (WI.AuditTestGroupContentView.prototype.hidden):
+        (WI.AuditTestGroupContentView.prototype.showRunningPlaceholder):
+        (WI.AuditTestGroupContentView.prototype._updateClassList): Added.
+        (WI.AuditTestGroupContentView.prototype._updateLevelScopeBar):
+        (WI.AuditTestGroupContentView.prototype._addTest): Added.
+        (WI.AuditTestGroupContentView.prototype._handleTestGroupTestAdded): Added.
+        (WI.AuditTestGroupContentView.prototype._handleTestGroupTestRemoved): Added.
+        * UserInterface/Views/AuditTestGroupContentView.css:
+        (.content-view.audit-test-group > section > .audit-test-group > header): Added.
+        (.content-view.audit-test-group.contains-test-case > header):
+        (.content-view.audit-test-group > section > .audit-test-group.contains-test-case > header): Added.
+        (.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case, .content-view.audit-test-group + .content-view.audit-test-case): Added.
+        (.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case): Deleted.
+        When in edit mode, add buttons for removing the audit and adding a new sub-audit (using the
+        new `WI.CreateAuditPopover` popover).
+
+        * UserInterface/Views/AuditTestCaseContentView.js:
+        (WI.AuditTestCaseContentView):
+        (WI.AuditTestCaseContentView.prototype.initialLayout):
+        (WI.AuditTestCaseContentView.prototype.layout):
+        (WI.AuditTestCaseContentView.prototype.showRunningPlaceholder):
+        * UserInterface/Views/AuditTestCaseContentView.css:
+        (.content-view-container > .content-view.audit-test-case): Added.
+        (.content-view-container > .content-view.audit-test-case > header):
+        (.content-view-container > .content-view.audit-test-case.manager-editing > header h1 > img): Added.
+        (.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view, .editor):first-child): Added.
+        (.content-view-container > .content-view.audit-test-case > section): Added.
+        (.content-view-container > .content-view.audit-test-case > section, .content-view-container > .content-view.audit-test-case > section :is(.editor, .CodeMirror)): Added.
+        (.content-view.audit-test-case.manager-editing.disabled:not(.editable) > header h1 > img): Added.
+        (.content-view.audit-test-case > section > :not(.message-text-view, .editor)): Added.
+        (.content-view.audit-test-case > section > :not(.message-text-view, .editor):last-child): Added.
+        (.content-view.audit-test-case > section > :not(.message-text-view, .editor) + :not(.message-text-view, .editor)): Added.
+        (@media (prefers-color-scheme: dark) .content-view.audit-test-case.manager-editing > header h1 > img): Added.
+        (.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view):first-child): Deleted.
+        (.content-view.audit-test-case > section > :not(.message-text-view)): Deleted.
+        (.content-view.audit-test-case > section > :not(.message-text-view):last-child): Deleted.
+        (.content-view.audit-test-case > section > :not(.message-text-view) + :not(.message-text-view)): Deleted.
+        (.content-view.audit-test-case > section .CodeMirror): Deleted.
+        When in edit mode, replace the icon with a (X) to remove the audit and show a `CodeMirror`
+        instance to allow editing the content.
+
+        * UserInterface/Views/AuditTreeElement.js:
+        (WI.AuditTreeElement):
+        (WI.AuditTreeElement.expandedSettingKey): Added.
+        (WI.AuditTreeElement.prototype.onattach):
+        (WI.AuditTreeElement.prototype.ondelete):
+        (WI.AuditTreeElement.prototype.canSelectOnMouseDown): Added.
+        (WI.AuditTreeElement.prototype.populateContextMenu):
+        (WI.AuditTreeElement.prototype._handleTestNameChanged): Added.
+        (WI.AuditTreeElement.prototype._handleTestSupportedChanged): Added.
+        (WI.AuditTreeElement.prototype._handleTestGroupTestAdded): Added.
+        * UserInterface/Views/AuditTreeElement.css:
+        (.tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img): Added.
+        (body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active).selected:hover > .status > img, body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit.test-case.selected > .status > .indeterminate-progress-spinner, body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit.test-group.selected > .status > progress): Added.
+        (.tree-outline .item.audit:not(:hover) > .status > img.show-on-hover, .tree-outline .item.audit.test-group.expanded:not(.unsupported, .editing-audits):not(:hover) > .status): Added.
+        (.tree-outline .item.audit.manager-active > .status > img.show-on-hover, .tree-outline .item.audit.manager-active > .status > img.show-on-hover, .tree-outline .item.audit.test-group.expanded:not(.editing-audits):hover > .status > :not(img), .tree-outline .item.audit.test-group-result.expanded > .status, .tree-outline .item.audit.unsupported + .children .item.audit.unsupported  > .status > img): Added.
+        (@media (prefers-color-scheme: dark) .tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img): Added.
+        (.tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active) > .status:hover > img): Deleted.
+        (.tree-outline .item.audit > .status:not(:hover) > img.show-on-hover, .tree-outline .item.audit.test-group.expanded:not(.unsupported, .editing-audits) > .status:not(:hover)): Deleted.
+        (.tree-outline .item.audit.manager-active > .status > img.show-on-hover, .tree-outline .item.audit.test-group.expanded:not(.editing-audits) > .status:hover > :not(img), .tree-outline .item.audit.test-group-result.expanded > .status, .tree-outline .item.audit.unsupported + .children .item.audit.unsupported  > .status > img): Deleted.
+        Add context menu items for duplicating/deleting tests when in edit mode. Adjust the label
+        and disabled state of existing context menu items for clarity.
+
+        * UserInterface/Views/CreateAuditPopover.js: Added.
+        (WI.CreateAuditPopover):
+        (WI.CreateAuditPopover.prototype.get audit):
+        (WI.CreateAuditPopover.prototype.show):
+        (WI.CreateAuditPopover.prototype.dismiss.const.testFunction):
+        (WI.CreateAuditPopover.prototype.dismiss):
+        (WI.CreateAuditPopover.prototype._presentOverTargetElement):
+        * UserInterface/Views/CreateAuditPopover.css: Added.
+        (.popover .create-audit-content):
+        (.popover .create-audit-content > .editor-wrapper):
+        (.popover .create-audit-content > .editor-wrapper > .reference-page-link):
+        New popover for creating an audit:
+
+            [<select> of group or test case] [<input> for name] (?)
+
+        * UserInterface/Views/Main.css:
+        (.navigation-item-help > .navigation-bar):
+        (.message-text-view > .navigation-item-help + .navigation-item-help): Added.
+        Add styles for when multiple navigation help items are used in the same message text view.
+
+        * UserInterface/Views/Variables.css:
+        (:root):
+        (@media (prefers-color-scheme: dark) :root):
+        Add `--filter-invert` to light mode too.
+
+        * UserInterface/Base/Utilities.js:
+        (HTMLInputElement.prototype.autosize):
+        * UserInterface/Views/CanvasOverviewContentView.js:
+        (WI.CanvasOverviewContentView):
+        (WI.CanvasOverviewContentView.prototype._updateRecordingAutoCaptureInputElementSize):
+        (WI.CanvasOverviewContentView.get recordingAutoCaptureInputMargin): Deleted.
+        * UserInterface/Views/CanvasOverviewContentView.css:
+        (.navigation-bar > .item.canvas-recording-auto-capture > label > input):
+        (.navigation-bar > .item.canvas-recording-auto-capture > label > input::-webkit-inner-spin-button): Deleted.
+        Create a helper function for autosizing an `<input>`.
+
+        * UserInterface/Views/AuditTabContentView.js:
+        (WI.AuditTabContentView):
+        (WI.AuditTabContentView.prototype.initialLayout):
+        Remove the back/foward arrows as they can get into an inconsistent state when editing.
+        Drive-by: update the drop zone text for clarity.
+
+        * UserInterface/Views/SearchSidebarPanel.js:
+        (WI.SearchSidebarPanel.prototype.showDefaultContentView):
+        Drive-by: add period to help text.
+
+        * UserInterface/Main.html:
+        * Localizations/en.lproj/localizedStrings.js:
+
</ins><span class="cx"> 2020-08-27  Carlos Garcia Campos  <cgarcia@igalia.com>
</span><span class="cx"> 
</span><span class="cx">         [GTK] Include the run loop source name in frame rendering timeline
</span></span></pre></div>
<a id="trunkSourceWebInspectorUILocalizationsenlprojlocalizedStringsjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js   2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js      2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -108,6 +108,8 @@
</span><span class="cx"> localizedStrings["Add New Probe Expression"] = "Add New Probe Expression";
</span><span class="cx"> localizedStrings["Add New Watch Expression"] = "Add New Watch Expression";
</span><span class="cx"> localizedStrings["Add Pattern"] = "Add Pattern";
</span><ins>+/* Text of button to add a new audit test case to the currently shown audit group. */
+localizedStrings["Add Test Case @ Audit Tab - Group"] = "Add Test Case";
</ins><span class="cx"> localizedStrings["Add a Class"] = "Add a Class";
</span><span class="cx"> localizedStrings["Add new breakpoint action after this action"] = "Add new breakpoint action after this action";
</span><span class="cx"> localizedStrings["Add new rule"] = "Add new rule";
</span><span class="lines">@@ -354,9 +356,12 @@
</span><span class="cx"> localizedStrings["Could not fetch properties. Object may no longer exist."] = "Could not fetch properties. Object may no longer exist.";
</span><span class="cx"> localizedStrings["Count"] = "Count";
</span><span class="cx"> localizedStrings["Create %s Rule"] = "Create %s Rule";
</span><ins>+/* Title of button that creates a new audit. */
+localizedStrings["Create @ Audit Tab Navigation Sidebar"] = "Create";
</ins><span class="cx"> localizedStrings["Create Breakpoint"] = "Create Breakpoint";
</span><span class="cx"> localizedStrings["Create Local Override"] = "Create Local Override";
</span><span class="cx"> localizedStrings["Create Resource"] = "Create Resource";
</span><ins>+localizedStrings["Create audit:"] = "Create audit:";
</ins><span class="cx"> localizedStrings["Cross-Origin Restrictions"] = "Cross-Origin Restrictions";
</span><span class="cx"> localizedStrings["Current"] = "Current";
</span><span class="cx"> localizedStrings["Current State"] = "Current State";
</span><span class="lines">@@ -390,6 +395,7 @@
</span><span class="cx"> /* Tooltip for a time range bar that represents when a CSS animation/transition is delayed */
</span><span class="cx"> localizedStrings["Delay"] = "Delay";
</span><span class="cx"> localizedStrings["Delete"] = "Delete";
</span><ins>+localizedStrings["Delete Audit"] = "Delete Audit";
</ins><span class="cx"> localizedStrings["Delete Blackbox"] = "Delete Blackbox";
</span><span class="cx"> localizedStrings["Delete Breakpoint"] = "Delete Breakpoint";
</span><span class="cx"> localizedStrings["Delete Breakpoints"] = "Delete Breakpoints";
</span><span class="lines">@@ -407,6 +413,7 @@
</span><span class="cx"> localizedStrings["Device Settings"] = "Device Settings";
</span><span class="cx"> localizedStrings["Diagnoses common accessibility problems affecting screen readers and other assistive technology."] = "Diagnoses common accessibility problems affecting screen readers and other assistive technology.";
</span><span class="cx"> localizedStrings["Dimensions"] = "Dimensions";
</span><ins>+localizedStrings["Disable Audit"] = "Disable Audit";
</ins><span class="cx"> localizedStrings["Disable Breakpoint"] = "Disable Breakpoint";
</span><span class="cx"> localizedStrings["Disable Breakpoints"] = "Disable Breakpoints";
</span><span class="cx"> localizedStrings["Disable Descendant Breakpoints"] = "Disable Descendant Breakpoints";
</span><span class="lines">@@ -440,6 +447,7 @@
</span><span class="cx"> localizedStrings["Download Web Archive"] = "Download Web Archive";
</span><span class="cx"> localizedStrings["Dropped Element"] = "Dropped Element";
</span><span class="cx"> localizedStrings["Dropped Node"] = "Dropped Node";
</span><ins>+localizedStrings["Duplicate Audit"] = "Duplicate Audit";
</ins><span class="cx"> localizedStrings["Duplicate Selector"] = "Duplicate Selector";
</span><span class="cx"> localizedStrings["Duplicate property"] = "Duplicate property";
</span><span class="cx"> localizedStrings["Duration"] = "Duration";
</span><span class="lines">@@ -453,6 +461,7 @@
</span><span class="cx"> localizedStrings["Edge of sRGB color space"] = "Edge of sRGB color space";
</span><span class="cx"> localizedStrings["Edit"] = "Edit";
</span><span class="cx"> localizedStrings["Edit %s"] = "Edit %s";
</span><ins>+localizedStrings["Edit Audit"] = "Edit Audit";
</ins><span class="cx"> localizedStrings["Edit Breakpoint\u2026"] = "Edit Breakpoint\u2026";
</span><span class="cx"> localizedStrings["Edit Local Override\u2026"] = "Edit Local Override\u2026";
</span><span class="cx"> localizedStrings["Edit \u201Cbox-shadow\u201D"] = "Edit \u201Cbox-shadow\u201D";
</span><span class="lines">@@ -460,6 +469,8 @@
</span><span class="cx"> localizedStrings["Edit \u201Cspring\u201D function"] = "Edit \u201Cspring\u201D function";
</span><span class="cx"> localizedStrings["Edit configuration"] = "Edit configuration";
</span><span class="cx"> localizedStrings["Edit custom gradient"] = "Edit custom gradient";
</span><ins>+/* Title of icon indiciating that the selected audit is being edited. */
+localizedStrings["Editing Audit @ Audit Tab - Test Case"] = "Editing audit";
</ins><span class="cx"> localizedStrings["Editing audits"] = "Editing audits";
</span><span class="cx"> localizedStrings["Element"] = "Element";
</span><span class="cx"> localizedStrings["Element Selection:"] = "Element Selection:";
</span><span class="lines">@@ -494,6 +505,7 @@
</span><span class="cx"> /* Name of Elements Tab */
</span><span class="cx"> localizedStrings["Elements Tab Name"] = "Elements";
</span><span class="cx"> localizedStrings["Emulate User Gesture"] = "Emulate User Gesture";
</span><ins>+localizedStrings["Enable Audit"] = "Enable Audit";
</ins><span class="cx"> localizedStrings["Enable Breakpoint"] = "Enable Breakpoint";
</span><span class="cx"> localizedStrings["Enable Breakpoints"] = "Enable Breakpoints";
</span><span class="cx"> localizedStrings["Enable Descendant Breakpoints"] = "Enable Descendant Breakpoints";
</span><span class="lines">@@ -527,6 +539,8 @@
</span><span class="cx"> localizedStrings["Ensure that values for \u0022%s\u0022 are valid."] = "Ensure that values for \u0022%s\u0022 are valid.";
</span><span class="cx"> localizedStrings["Entire Recording"] = "Entire Recording";
</span><span class="cx"> localizedStrings["Error"] = "Error";
</span><ins>+/* Title of icon indicating that the selected audit threw an error. */
+localizedStrings["Error @ Audit Tab - Test Case"] = "Error";
</ins><span class="cx"> localizedStrings["Error: "] = "Error: ";
</span><span class="cx"> localizedStrings["Errors"] = "Errors";
</span><span class="cx"> localizedStrings["Errors:"] = "Errors:";
</span><span class="lines">@@ -550,12 +564,13 @@
</span><span class="cx"> localizedStrings["Experimental"] = "Experimental";
</span><span class="cx"> localizedStrings["Export"] = "Export";
</span><span class="cx"> localizedStrings["Export (%s)"] = "Export (%s)";
</span><ins>+localizedStrings["Export Audit"] = "Export Audit";
</ins><span class="cx"> localizedStrings["Export HAR"] = "Export HAR";
</span><span class="cx"> localizedStrings["Export Result"] = "Export Result";
</span><del>-localizedStrings["Export Test"] = "Export Test";
</del><span class="cx"> localizedStrings["Export recording (%s)"] = "Export recording (%s)";
</span><span class="cx"> localizedStrings["Export recording (%s)\nShift-click to export a HTML reduction"] = "Export recording (%s)\nShift-click to export a HTML reduction";
</span><del>-localizedStrings["Export result (%s)"] = "Export result (%s)";
</del><ins>+/* Tooltip for button that exports the most recent result after running an audit. */
+localizedStrings["Export result (%s) @ Audit Tab"] = "Export result (%s)";
</ins><span class="cx"> localizedStrings["Expression"] = "Expression";
</span><span class="cx"> localizedStrings["Extension Scripts"] = "Extension Scripts";
</span><span class="cx"> localizedStrings["Extension Style Sheets"] = "Extension Style Sheets";
</span><span class="lines">@@ -563,6 +578,8 @@
</span><span class="cx"> localizedStrings["Extra Scripts"] = "Extra Scripts";
</span><span class="cx"> localizedStrings["Extra Style Sheets"] = "Extra Style Sheets";
</span><span class="cx"> localizedStrings["Fade unexecuted code"] = "Fade unexecuted code";
</span><ins>+/* Title of icon indicating that the selected audit failed. */
+localizedStrings["Fail @ Audit Tab - Test Case"] = "Fail";
</ins><span class="cx"> localizedStrings["Failed to upgrade"] = "Failed to upgrade";
</span><span class="cx"> localizedStrings["Failure status code"] = "Failure status code";
</span><span class="cx"> /* Resource loaded via 'fetch' method */
</span><span class="lines">@@ -628,6 +645,8 @@
</span><span class="cx"> /* Name of Graphics Tab */
</span><span class="cx"> localizedStrings["Graphics Tab Name"] = "Graphics";
</span><span class="cx"> localizedStrings["Group"] = "Group";
</span><ins>+/* Dropdown option inside the popover used to creating an audit group. */
+localizedStrings["Group @ Audit Tab Navigation Sidebar"] = "Group";
</ins><span class="cx"> localizedStrings["Group By Resource"] = "Group By Resource";
</span><span class="cx"> localizedStrings["Group Media Requests"] = "Group Media Requests";
</span><span class="cx"> /* Group DOM event listeners by DOM event */
</span><span class="lines">@@ -681,9 +700,10 @@
</span><span class="cx"> localizedStrings["Images:"] = "Images:";
</span><span class="cx"> localizedStrings["Immediate Pause Requested"] = "Immediate Pause Requested";
</span><span class="cx"> localizedStrings["Import"] = "Import";
</span><del>-localizedStrings["Import Audit"] = "Import Audit";
</del><ins>+localizedStrings["Import Audit or Result"] = "Import Audit or Result";
</ins><span class="cx"> localizedStrings["Import HAR"] = "Import HAR";
</span><span class="cx"> localizedStrings["Import Recording"] = "Import Recording";
</span><ins>+localizedStrings["Import audit or result"] = "Import audit or result";
</ins><span class="cx"> localizedStrings["Imported"] = "Imported";
</span><span class="cx"> localizedStrings["Imported - %s"] = "Imported - %s";
</span><span class="cx"> localizedStrings["Imported \u2014 %s"] = "Imported \u2014 %s";
</span><span class="lines">@@ -863,6 +883,8 @@
</span><span class="cx"> localizedStrings["Nodes"] = "Nodes";
</span><span class="cx"> localizedStrings["None"] = "None";
</span><span class="cx"> localizedStrings["Not found"] = "Not found";
</span><ins>+/* Title of icon indicating that the selected audit has not been run yet. */
+localizedStrings["Not yet run @ Audit Tab - Test Case"] = "Not yet run";
</ins><span class="cx"> localizedStrings["Object Graph"] = "Object Graph";
</span><span class="cx"> localizedStrings["Object Store"] = "Object Store";
</span><span class="cx"> localizedStrings["Observer Callback"] = "Observer Callback";
</span><span class="lines">@@ -909,6 +931,8 @@
</span><span class="cx"> localizedStrings["Paints @ Column title"] = "Paints";
</span><span class="cx"> localizedStrings["Parent"] = "Parent";
</span><span class="cx"> localizedStrings["Partial Garbage Collection"] = "Partial Garbage Collection";
</span><ins>+/* Title of icon indicating that the selected audit passed with no issues. */
+localizedStrings["Pass @ Audit Tab - Test Case"] = "Pass";
</ins><span class="cx"> localizedStrings["Passive"] = "Passive";
</span><span class="cx"> localizedStrings["Path"] = "Path";
</span><span class="cx"> localizedStrings["Pause Processing"] = "Pause Processing";
</span><span class="lines">@@ -932,11 +956,15 @@
</span><span class="cx"> localizedStrings["Prefer Shorthands"] = "Prefer Shorthands";
</span><span class="cx"> localizedStrings["Prefer indent using:"] = "Prefer indent using:";
</span><span class="cx"> localizedStrings["Preserve Log"] = "Preserve Log";
</span><del>-localizedStrings["Press %s to import a test or result file"] = "Press %s to import a test or result file";
</del><ins>+localizedStrings["Press %s to create a new audit."] = "Press %s to create a new audit.";
+localizedStrings["Press %s to enable audits."] = "Press %s to enable audits.";
+localizedStrings["Press %s to import an audit or a result."] = "Press %s to import an audit or a result.";
</ins><span class="cx"> localizedStrings["Press %s to load a recording from file."] = "Press %s to load a recording from file.";
</span><del>-localizedStrings["Press %s to see recent searches"] = "Press %s to see recent searches";
-localizedStrings["Press %s to start running the audit"] = "Press %s to start running the audit";
-localizedStrings["Press %s to stop editing"] = "Press %s to stop editing";
</del><ins>+localizedStrings["Press %s to see recent searches."] = "Press %s to see recent searches.";
+localizedStrings["Press %s to start editing audits."] = "Press %s to start editing audits.";
+localizedStrings["Press %s to start running the audit."] = "Press %s to start running the audit.";
+localizedStrings["Press %s to stop editing audits."] = "Press %s to stop editing audits.";
+localizedStrings["Press %s to stop running."] = "Press %s to stop running.";
</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">@@ -1092,6 +1120,8 @@
</span><span class="cx"> localizedStrings["Security"] = "Security";
</span><span class="cx"> localizedStrings["Security Issue"] = "Security Issue";
</span><span class="cx"> localizedStrings["Security Origin"] = "Security Origin";
</span><ins>+localizedStrings["Select an audit in the navigation sidebar to edit it."] = "Select an audit in the navigation sidebar to edit it.";
+localizedStrings["Select an audit in the navigation sidebar to view its results."] = "Select an audit in the navigation sidebar to view its results.";
</ins><span class="cx"> localizedStrings["Select baseline snapshot"] = "Select baseline snapshot";
</span><span class="cx"> localizedStrings["Select comparison snapshot"] = "Select comparison snapshot";
</span><span class="cx"> localizedStrings["Selected"] = "Selected";
</span><span class="lines">@@ -1185,6 +1215,7 @@
</span><span class="cx"> localizedStrings["Staging:"] = "Staging:";
</span><span class="cx"> localizedStrings["Stalled"] = "Stalled";
</span><span class="cx"> localizedStrings["Start"] = "Start";
</span><ins>+localizedStrings["Start Audit"] = "Start Audit";
</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">@@ -1237,6 +1268,8 @@
</span><span class="cx"> localizedStrings["Take snapshot"] = "Take snapshot";
</span><span class="cx"> localizedStrings["Target"] = "Target";
</span><span class="cx"> localizedStrings["Template Content"] = "Template Content";
</span><ins>+/* Dropdown option inside the popover used to creating an audit test case. */
+localizedStrings["Test Case @ Audit Tab Navigation Sidebar"] = "Test Case";
</ins><span class="cx"> localizedStrings["Text"] = "Text";
</span><span class="cx"> localizedStrings["Text Frame"] = "Text Frame";
</span><span class="cx"> localizedStrings["Text Node"] = "Text Node";
</span><span class="lines">@@ -1280,6 +1313,7 @@
</span><span class="cx"> localizedStrings["This resource was loaded from a service worker"] = "This resource was loaded from a service worker";
</span><span class="cx"> localizedStrings["This resource was loaded from the disk cache"] = "This resource was loaded from the disk cache";
</span><span class="cx"> localizedStrings["This resource was loaded from the memory cache"] = "This resource was loaded from the memory cache";
</span><ins>+localizedStrings["This test should not run because it should be unsupported."] = "This test should not run because it should be unsupported.";
</ins><span class="cx"> localizedStrings["This test will pass with a variety of accessibility information about the <body> element."] = "This test will pass with a variety of accessibility information about the <body> element.";
</span><span class="cx"> localizedStrings["This test will pass with all DOM nodes that have a computed role of \u0022link\u0022."] = "This test will pass with all DOM nodes that have a computed role of \u0022link\u0022.";
</span><span class="cx"> localizedStrings["This test will pass with all child nodes that are selected (\u0022%s\u0022) of the <body> element in the accessibility tree."] = "This test will pass with all child nodes that are selected (\u0022%s\u0022) of the <body> element in the accessibility tree.";
</span><span class="lines">@@ -1349,6 +1383,8 @@
</span><span class="cx"> localizedStrings["Unknown Location"] = "Unknown Location";
</span><span class="cx"> localizedStrings["Unknown error"] = "Unknown error";
</span><span class="cx"> localizedStrings["Unknown node"] = "Unknown node";
</span><ins>+/* Title of icon indicating that the selected audit is not able to be run (i.e. unsupported). */
+localizedStrings["Unsupported @ Audit Tab - Test Case"] = "Unsupported";
</ins><span class="cx"> localizedStrings["Unsupported property name"] = "Unsupported property name";
</span><span class="cx"> localizedStrings["Unsupported property value"] = "Unsupported property value";
</span><span class="cx"> localizedStrings["Untitled"] = "Untitled";
</span><span class="lines">@@ -1385,6 +1421,8 @@
</span><span class="cx"> localizedStrings["Waiting for canvas contexts created by script or CSS."] = "Waiting for canvas contexts created by script or CSS.";
</span><span class="cx"> localizedStrings["Waiting for frames\u2026"] = "Waiting for frames\u2026";
</span><span class="cx"> localizedStrings["Waiting for transitions created by CSS."] = "Waiting for transitions created by CSS.";
</span><ins>+/* Title of icon indicating that the selected audit passed with issues (i.e. warnings). */
+localizedStrings["Warn @ Audit Tab - Test Case"] = "Warn";
</ins><span class="cx"> localizedStrings["Warning: "] = "Warning: ";
</span><span class="cx"> localizedStrings["Warnings"] = "Warnings";
</span><span class="cx"> localizedStrings["Watch Expressions"] = "Watch Expressions";
</span><span class="lines">@@ -1471,8 +1509,8 @@
</span><span class="cx"> localizedStrings["\u0022%s\u0022 has an invalid \u0022%s\u0022 value"] = "\u0022%s\u0022 has an invalid \u0022%s\u0022 value";
</span><span class="cx"> localizedStrings["\u0022%s\u0022 is not JSON serializable"] = "\u0022%s\u0022 is not JSON serializable";
</span><span class="cx"> localizedStrings["\u0022%s\u0022 is not valid for %s"] = "\u0022%s\u0022 is not valid for %s";
</span><ins>+localizedStrings["\u0022%s\u0022 is too new to run in the inspected page"] = "\u0022%s\u0022 is too new to run in the inspected page";
</ins><span class="cx"> localizedStrings["\u0022%s\u0022 is too new to run in this Web Inspector"] = "\u0022%s\u0022 is too new to run in this Web Inspector";
</span><del>-localizedStrings["\u0022%s\u0022 is too new to run on this inspected page"] = "\u0022%s\u0022 is too new to run on this inspected page";
</del><span class="cx"> localizedStrings["\u0022%s\u0022 must be a %s"] = "\u0022%s\u0022 must be a %s";
</span><span class="cx"> localizedStrings["\u0022%s\u0022 must be an %s"] = "\u0022%s\u0022 must be an %s";
</span><span class="cx"> localizedStrings["\u0022%s\u0022 threw an error"] = "\u0022%s\u0022 threw an error";
</span><span class="lines">@@ -1504,6 +1542,10 @@
</span><span class="cx"> localizedStrings["time before stopping"] = "time before stopping";
</span><span class="cx"> localizedStrings["times before stopping"] = "times before stopping";
</span><span class="cx"> localizedStrings["toggle"] = "toggle";
</span><ins>+/* Warning text shown if the version number in the 'supports' input is too new. */
+localizedStrings["too new to run in the inspected page @ Audit Tab"] = "too new to run in the inspected page";
+/* Warning text shown if the version number in the 'supports' input is too new. */
+localizedStrings["too new to run in this Web Inspector @ Audit Tab"] = "too new to run in this Web Inspector";
</ins><span class="cx"> localizedStrings["unknown %s \u0022%s\u0022"] = "unknown %s \u0022%s\u0022";
</span><span class="cx"> localizedStrings["unsupported %s"] = "unsupported %s";
</span><span class="cx"> localizedStrings["unsupported HAR version"] = "unsupported HAR version";
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceBaseUtilitiesjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js      2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js 2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -484,6 +484,29 @@
</span><span class="cx">     value: Element.prototype.createChild
</span><span class="cx"> });
</span><span class="cx"> 
</span><ins>+(function() {
+    const fontSymbol = Symbol("font");
+
+    Object.defineProperty(HTMLInputElement.prototype, "autosize",
+    {
+        value(extra = 0)
+        {
+            extra += 6; // UserAgent styles add 1px padding and 2px border.
+            if (this.type === "number")
+                extra += 13; // Number input inner spin button width.
+            extra += 2; // Add extra pixels for the cursor.
+
+            WI.ImageUtilities.scratchCanvasContext2D((context) => {
+                this[fontSymbol] ||= window.getComputedStyle(this).font;
+
+                context.font = this[fontSymbol];
+                let textMetrics = context.measureText(this.value || this.placeholder);
+                this.style.setProperty("width", (textMetrics.width + extra) + "px");
+            });
+        },
+    });
+})();
+
</ins><span class="cx"> Object.defineProperty(Event.prototype, "stop",
</span><span class="cx"> {
</span><span class="cx">     value()
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceControllersAuditManagerjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js    2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js       2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -103,7 +103,7 @@
</span><span class="cx"> 
</span><span class="cx">             let disabledDefaultTests = [];
</span><span class="cx">             let saveDisabledDefaultTest = (test) => {
</span><del>-                if (test.disabled)
</del><ins>+                if (test.supported && test.disabled)
</ins><span class="cx">                     disabledDefaultTests.push(test.name);
</span><span class="cx"> 
</span><span class="cx">                 if (test instanceof WI.AuditTestGroup) {
</span><span class="lines">@@ -113,7 +113,7 @@
</span><span class="cx">             };
</span><span class="cx"> 
</span><span class="cx">             for (let test of this._tests) {
</span><del>-                if (test.__default)
</del><ins>+                if (test.default)
</ins><span class="cx">                     saveDisabledDefaultTest(test);
</span><span class="cx">                 else
</span><span class="cx">                     WI.objectStores.audits.putObject(test);
</span><span class="lines">@@ -141,6 +141,8 @@
</span><span class="cx">         let mainResource = WI.networkManager.mainFrame.mainResource;
</span><span class="cx"> 
</span><span class="cx">         this._runningState = WI.AuditManager.RunningState.Active;
</span><ins>+        this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged);
+
</ins><span class="cx">         this._runningTests = tests;
</span><span class="cx">         for (let test of this._runningTests)
</span><span class="cx">             test.clearResult();
</span><span class="lines">@@ -156,10 +158,10 @@
</span><span class="cx">             if (target.hasDomain("Audit"))
</span><span class="cx">                 await target.AuditAgent.setup();
</span><span class="cx"> 
</span><del>-            let topLevelTest = this._topLevelTestForTest(test);
</del><ins>+            let topLevelTest = test.topLevelTest;
</ins><span class="cx">             console.assert(topLevelTest || window.InspectorTest, "No matching top-level test found", test);
</span><span class="cx">             if (topLevelTest)
</span><del>-                await topLevelTest.setup();
</del><ins>+                await topLevelTest.runSetup();
</ins><span class="cx"> 
</span><span class="cx">             await test.start();
</span><span class="cx"> 
</span><span class="lines">@@ -170,6 +172,8 @@
</span><span class="cx">         let result = this._runningTests.map((test) => test.result).filter((result) => !!result);
</span><span class="cx"> 
</span><span class="cx">         this._runningState = WI.AuditManager.RunningState.Inactive;
</span><ins>+        this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged);
+
</ins><span class="cx">         this._runningTests = [];
</span><span class="cx"> 
</span><span class="cx">         this._addResult(result);
</span><span class="lines">@@ -190,6 +194,7 @@
</span><span class="cx">             return;
</span><span class="cx"> 
</span><span class="cx">         this._runningState = WI.AuditManager.RunningState.Stopping;
</span><ins>+        this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged);
</ins><span class="cx"> 
</span><span class="cx">         for (let test of this._runningTests)
</span><span class="cx">             test.stop();
</span><span class="lines">@@ -218,7 +223,7 @@
</span><span class="cx">             return;
</span><span class="cx"> 
</span><span class="cx">         if (object instanceof WI.AuditTestBase) {
</span><del>-            this._addTest(object);
</del><ins>+            this.addTest(object);
</ins><span class="cx">             WI.objectStores.audits.putObject(object);
</span><span class="cx">         } else if (object instanceof WI.AuditTestResultBase)
</span><span class="cx">             this._addResult(object);
</span><span class="lines">@@ -257,14 +262,30 @@
</span><span class="cx">                 const key = null;
</span><span class="cx">                 WI.objectStores.audits.associateObject(test, key, payload);
</span><span class="cx"> 
</span><del>-                this._addTest(test);
</del><ins>+                this.addTest(test);
</ins><span class="cx">             }
</span><span class="cx">         });
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    addTest(test)
+    {
+        console.assert(test instanceof WI.AuditTestBase, test);
+        console.assert(!this._tests.includes(test), test);
+
+        this._tests.push(test);
+
+        this.dispatchEventToListeners(WI.AuditManager.Event.TestAdded, {test});
+    }
+
</ins><span class="cx">     removeTest(test)
</span><span class="cx">     {
</span><del>-        if (test.__default) {
</del><ins>+        console.assert(this.editing);
+        console.assert(test instanceof WI.AuditTestBase, test);
+        console.assert(this._tests.includes(test) || test.default, test);
+
+        if (test.default) {
+            test.clearResult();
+
</ins><span class="cx">             if (test.disabled) {
</span><span class="cx">                 InspectorFrontendHost.beep();
</span><span class="cx">                 return;
</span><span class="lines">@@ -279,6 +300,8 @@
</span><span class="cx">             return;
</span><span class="cx">         }
</span><span class="cx"> 
</span><ins>+        console.assert(test.editable, test);
+
</ins><span class="cx">         this._tests.remove(test);
</span><span class="cx"> 
</span><span class="cx">         this.dispatchEventToListeners(WI.AuditManager.Event.TestRemoved, {test});
</span><span class="lines">@@ -288,13 +311,6 @@
</span><span class="cx"> 
</span><span class="cx">     // Private
</span><span class="cx"> 
</span><del>-    _addTest(test)
-    {
-        this._tests.push(test);
-
-        this.dispatchEventToListeners(WI.AuditManager.Event.TestAdded, {test});
-    }
-
</del><span class="cx">     _addResult(result)
</span><span class="cx">     {
</span><span class="cx">         if (!result || (Array.isArray(result) && !result.length))
</span><span class="lines">@@ -308,28 +324,6 @@
</span><span class="cx">         });
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    _topLevelTestForTest(test)
-    {
-        function walk(group) {
-            if (group === test)
-                return true;
-            if (group instanceof WI.AuditTestGroup) {
-                for (let subtest of group.tests) {
-                    if (walk(subtest))
-                        return true;
-                }
-            }
-            return false;
-        }
-
-        for (let topLevelTest of this._tests) {
-            if (walk(topLevelTest))
-                return topLevelTest;
-        }
-
-        return null;
-    }
-
</del><span class="cx">     _handleFrameMainResourceDidChange(event)
</span><span class="cx">     {
</span><span class="cx">         if (!event.target.isMainFrame())
</span><span class="lines">@@ -460,6 +454,10 @@
</span><span class="cx">             return {level: "pass", mainResource, resourceContent: WebInspectorAudit.Resources.getResourceContent(mainResource.id)};
</span><span class="cx">         };
</span><span class="cx"> 
</span><ins>+        const unsupported = function() {
+            throw Error("this test should not be supported.");
+        };
+
</ins><span class="cx">         const testMenuRoleForRequiredChildren = function() {
</span><span class="cx">             const relationships = {
</span><span class="cx">                 menu: ["menuitem", "menuitemcheckbox", "menuitemradio"],
</span><span class="lines">@@ -998,7 +996,7 @@
</span><span class="cx">         };
</span><span class="cx"> 
</span><span class="cx">         function removeWhitespace(func) {
</span><del>-            return func.toString().replace(/\s+/g, " ");
</del><ins>+            return WI.AuditTestCase.stringifyFunction(func, 8);
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         const defaultTests = [
</span><span class="lines">@@ -1038,6 +1036,7 @@
</span><span class="cx">                         new WI.AuditTestCase("getResourceContent", removeWhitespace(getResourceContent), {description: WI.UIString("This test will pass with the contents of the main resource."), supports: 3}),
</span><span class="cx">                     ], {description: WI.UIString("These tests demonstrate how to use %s to get information about loaded resources.").format(WI.unlocalizedString("WebInspectorAudit.Resources")), supports: 2}),
</span><span class="cx">                 ], {description: WI.UIString("These tests demonstrate how to use %s to access information not normally available to JavaScript.").format(WI.unlocalizedString("WebInspectorAudit")), supports: 1}),
</span><ins>+                new WI.AuditTestCase("unsupported", removeWhitespace(unsupported), {description: WI.UIString("This test should not run because it should be unsupported."), supports: Infinity}),
</ins><span class="cx">             ], {description: WI.UIString("These tests serve as a demonstration of the functionality and structure of audits.")}),
</span><span class="cx">             new WI.AuditTestGroup(WI.UIString("Accessibility"), [
</span><span class="cx">                 new WI.AuditTestCase("testMenuRoleForRequiredChildren", removeWhitespace(testMenuRoleForRequiredChildren), {description: WI.UIString("Ensure that element of role \u0022%s\u0022 and \u0022%s\u0022 have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("menu"), WI.unlocalizedString("menubar")), supports: 1}),
</span><span class="lines">@@ -1066,6 +1065,8 @@
</span><span class="cx">         ];
</span><span class="cx"> 
</span><span class="cx">         let checkDisabledDefaultTest = (test) => {
</span><ins>+            test.markAsDefault();
+
</ins><span class="cx">             if (this._disabledDefaultTestsSetting.value.includes(test.name))
</span><span class="cx">                 test.disabled = true;
</span><span class="cx"> 
</span><span class="lines">@@ -1078,8 +1079,7 @@
</span><span class="cx">         for (let test of defaultTests) {
</span><span class="cx">             checkDisabledDefaultTest(test);
</span><span class="cx"> 
</span><del>-            test.__default = true;
-            this._addTest(test);
</del><ins>+            this.addTest(test);
</ins><span class="cx">         }
</span><span class="cx">     }
</span><span class="cx"> };
</span><span class="lines">@@ -1093,6 +1093,7 @@
</span><span class="cx"> 
</span><span class="cx"> WI.AuditManager.Event = {
</span><span class="cx">     EditingChanged: "audit-manager-editing-changed",
</span><ins>+    RunningStateChanged: "audit-manager-running-state-changed",
</ins><span class="cx">     TestAdded: "audit-manager-test-added",
</span><span class="cx">     TestCompleted: "audit-manager-test-completed",
</span><span class="cx">     TestRemoved: "audit-manager-test-removed",
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceMainhtml"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Main.html (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Main.html      2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Main.html 2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -82,6 +82,7 @@
</span><span class="cx">     <link rel="stylesheet" href="Views/ContentViewContainer.css">
</span><span class="cx">     <link rel="stylesheet" href="Views/CookiePopover.css">
</span><span class="cx">     <link rel="stylesheet" href="Views/CookieStorageContentView.css">
</span><ins>+    <link rel="stylesheet" href="Views/CreateAuditPopover.css">
</ins><span class="cx">     <link rel="stylesheet" href="Views/DOMBreakpointTreeElement.css">
</span><span class="cx">     <link rel="stylesheet" href="Views/DOMEventsBreakdownView.css">
</span><span class="cx">     <link rel="stylesheet" href="Views/DOMNodeDetailsSidebarPanel.css">
</span><span class="lines">@@ -651,6 +652,7 @@
</span><span class="cx">     <script src="Views/CookiePopover.js"></script>
</span><span class="cx">     <script src="Views/CookieStorageContentView.js"></script>
</span><span class="cx">     <script src="Views/CookieStorageTreeElement.js"></script>
</span><ins>+    <script src="Views/CreateAuditPopover.js"></script>
</ins><span class="cx">     <script src="Views/DOMBreakpointTreeElement.js"></script>
</span><span class="cx">     <script src="Views/DOMEventsBreakdownView.js"></script>
</span><span class="cx">     <script src="Views/DOMNodeDetailsSidebarPanel.js"></script>
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestBasejs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js        2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js   2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -27,59 +27,111 @@
</span><span class="cx"> {
</span><span class="cx">     constructor(name, {description, supports, setup, disabled} = {})
</span><span class="cx">     {
</span><del>-        console.assert(typeof name === "string");
-        console.assert(!description || typeof description === "string");
-        console.assert(supports === undefined || typeof supports === "number");
-        console.assert(!setup || typeof setup === "string");
-        console.assert(disabled === undefined || typeof disabled === "boolean");
</del><ins>+        console.assert(typeof name === "string", name);
+        console.assert(!description || typeof description === "string", description);
+        console.assert(supports === undefined || typeof supports === "number", supports);
+        console.assert(!setup || typeof setup === "string", setup);
+        console.assert(disabled === undefined || typeof disabled === "boolean", disabled);
</ins><span class="cx"> 
</span><span class="cx">         super();
</span><span class="cx"> 
</span><span class="cx">         // This class should not be instantiated directly. Create a concrete subclass instead.
</span><del>-        console.assert(this.constructor !== WI.AuditTestBase && this instanceof WI.AuditTestBase);
</del><ins>+        console.assert(this.constructor !== WI.AuditTestBase && this instanceof WI.AuditTestBase, this);
</ins><span class="cx"> 
</span><span class="cx">         this._name = name;
</span><del>-        this._description = description || null;
-        this._supports = supports;
-        this._setup = setup || null;
</del><ins>+        this._description = description || "";
+        this._supports = supports ?? NaN;
+        this._setup = setup || "";
</ins><span class="cx"> 
</span><del>-        this._supported = true;
-        if (typeof this._supports === "number") {
-            if (this._supports > WI.AuditTestBase.Version) {
-                WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run in this Web Inspector").format(this.name));
-                this._supported = false;
-            } else if (this._supports > InspectorBackend.getVersion("Audit")) {
-                WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run on this inspected page").format(this.name));
-                this._supported = false;
-            }
-        }
</del><ins>+        this.determineIfSupported({warn: true});
</ins><span class="cx"> 
</span><del>-        if (!this.supported)
-            disabled = true;
-
</del><span class="cx">         this._runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive;
</span><span class="cx">         this._result = null;
</span><ins>+
+        this._parent = null;
+
+        this._default = false;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     // Public
</span><span class="cx"> 
</span><del>-    get name() { return this._name; }
-    get description() { return this._description; }
</del><span class="cx">     get runningState() { return this._runningState; }
</span><span class="cx">     get result() { return this._result; }
</span><ins>+    get supported() { return this._supported; }
</ins><span class="cx"> 
</span><del>-    get supported()
</del><ins>+    get name()
</ins><span class="cx">     {
</span><del>-        return this._supported;
</del><ins>+        return this._name;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    set supported(supported)
</del><ins>+    set name(name)
</ins><span class="cx">     {
</span><del>-        this._supported = supported;
-        if (!this._supported)
-            this.disabled = true;
</del><ins>+        console.assert(this.editable);
+        console.assert(WI.auditManager.editing);
+        console.assert(name && typeof name === "string", name);
+
+        if (name === this._name)
+            return;
+
+        let oldName = this._name;
+        this._name = name;
+        this.dispatchEventToListeners(WI.AuditTestBase.Event.NameChanged, {oldName});
</ins><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    get description()
+    {
+        return this._description;
+    }
+
+    set description(description)
+    {
+        console.assert(this.editable);
+        console.assert(WI.auditManager.editing);
+        console.assert(typeof description === "string", description);
+
+        if (description === this._description)
+            return;
+
+        this._description = description;
+    }
+
+    get supports()
+    {
+        return this._supports;
+    }
+
+    set supports(supports)
+    {
+        console.assert(this.editable);
+        console.assert(WI.auditManager.editing);
+        console.assert(typeof supports === "number", supports);
+
+        if (supports === this._supports)
+            return;
+
+        this._supports = supports;
+        this.determineIfSupported();
+    }
+
+    get setup()
+    {
+        return this._setup;
+    }
+
+    set setup(setup)
+    {
+        console.assert(this.editable);
+        console.assert(WI.auditManager.editing);
+        console.assert(typeof setup === "string", setup);
+
+        if (setup === this._setup)
+            return;
+
+        this._setup = setup;
+
+        this.clearResult();
+    }
+
</ins><span class="cx">     get disabled()
</span><span class="cx">     {
</span><span class="cx">         return this._runningState === WI.AuditManager.RunningState.Disabled;
</span><span class="lines">@@ -87,24 +139,37 @@
</span><span class="cx"> 
</span><span class="cx">     set disabled(disabled)
</span><span class="cx">     {
</span><del>-        console.assert(this._runningState === WI.AuditManager.RunningState.Disabled || this._runningState === WI.AuditManager.RunningState.Inactive);
-        if (this._runningState !== WI.AuditManager.RunningState.Disabled && this._runningState !== WI.AuditManager.RunningState.Inactive)
-            return;
</del><ins>+        this.updateDisabled(disabled);
+    }
</ins><span class="cx"> 
</span><del>-        if (!this.supported)
-            disabled = true;
</del><ins>+    get editable()
+    {
+        return !this._default;
+    }
</ins><span class="cx"> 
</span><del>-        let runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive;
-        if (runningState === this._runningState)
-            return;
</del><ins>+    get default()
+    {
+        return this._default;
+    }
</ins><span class="cx"> 
</span><del>-        this._runningState = runningState;
</del><ins>+    markAsDefault()
+    {
+        console.assert(!this._default);
+        this._default = true;
+    }
</ins><span class="cx"> 
</span><del>-        this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged);
</del><ins>+    get topLevelTest()
+    {
+        let test = this;
+        while (test._parent)
+            test = test._parent;
+        return test;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    async setup()
</del><ins>+    async runSetup()
</ins><span class="cx">     {
</span><ins>+        console.assert(this.topLevelTest === this);
+
</ins><span class="cx">         if (!this._setup)
</span><span class="cx">             return;
</span><span class="cx"> 
</span><span class="lines">@@ -148,11 +213,9 @@
</span><span class="cx">     {
</span><span class="cx">         // Called from WI.AuditManager.
</span><span class="cx"> 
</span><del>-        if (this.disabled)
</del><ins>+        if (!this._supported || this.disabled)
</ins><span class="cx">             return;
</span><span class="cx"> 
</span><del>-        console.assert(this.supported);
-
</del><span class="cx">         console.assert(WI.auditManager.runningState === WI.AuditManager.RunningState.Active);
</span><span class="cx"> 
</span><span class="cx">         console.assert(this._runningState === WI.AuditManager.RunningState.Inactive);
</span><span class="lines">@@ -171,12 +234,11 @@
</span><span class="cx">     stop()
</span><span class="cx">     {
</span><span class="cx">         // Called from WI.AuditManager.
</span><ins>+        // Overridden by sub-classes if needed.
</ins><span class="cx"> 
</span><del>-        if (this.disabled)
</del><ins>+        if (!this._supported || this.disabled)
</ins><span class="cx">             return;
</span><span class="cx"> 
</span><del>-        console.assert(this.supported);
-
</del><span class="cx">         console.assert(WI.auditManager.runningState === WI.AuditManager.RunningState.Stopping);
</span><span class="cx"> 
</span><span class="cx">         if (this._runningState !== WI.AuditManager.RunningState.Active)
</span><span class="lines">@@ -188,6 +250,8 @@
</span><span class="cx"> 
</span><span class="cx">     clearResult(options = {})
</span><span class="cx">     {
</span><ins>+        // Overridden by sub-classes if needed.
+
</ins><span class="cx">         if (!this._result)
</span><span class="cx">             return false;
</span><span class="cx"> 
</span><span class="lines">@@ -199,13 +263,44 @@
</span><span class="cx">         return true;
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    async clone()
+    {
+        console.assert(WI.auditManager.editing);
+
+        return this.constructor.fromPayload(this.toJSON());
+    }
+
+    remove()
+    {
+        console.assert(WI.auditManager.editing);
+
+        if (!this._parent || this._default) {
+            WI.auditManager.removeTest(this);
+            return;
+        }
+
+        console.assert(this.editable);
+        console.assert(this._parent instanceof WI.AuditTestGroup);
+        this._parent.removeTest(this);
+    }
+
</ins><span class="cx">     saveIdentityToCookie(cookie)
</span><span class="cx">     {
</span><del>-        cookie["audit-" + this.constructor.TypeIdentifier + "-name"] = this._name;
</del><ins>+        let path = [];
+        let test = this;
+        while (test) {
+            path.push(test.name);
+            test = test._parent;
+        }
+        path.reverse();
+
+        cookie["audit-path"] = path.join(",");
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     toJSON(key)
</span><span class="cx">     {
</span><ins>+        // Overridden by sub-classes if needed.
+
</ins><span class="cx">         let json = {
</span><span class="cx">             type: this.constructor.TypeIdentifier,
</span><span class="cx">             name: this._name,
</span><span class="lines">@@ -212,8 +307,8 @@
</span><span class="cx">         };
</span><span class="cx">         if (this._description)
</span><span class="cx">             json.description = this._description;
</span><del>-        if (typeof this._supports === "number")
-            json.supports = this._supports;
</del><ins>+        if (!isNaN(this._supports))
+            json.supports = Number.isFinite(this._supports) ? this._supports : WI.AuditTestBase.Version + 1;
</ins><span class="cx">         if (this._setup)
</span><span class="cx">             json.setup = this._setup;
</span><span class="cx">         if (key === WI.ObjectStore.toJSONSymbol)
</span><span class="lines">@@ -227,6 +322,76 @@
</span><span class="cx">     {
</span><span class="cx">         throw WI.NotImplementedError.subclassMustOverride();
</span><span class="cx">     }
</span><ins>+
+    determineIfSupported(options = {})
+    {
+        // Overridden by sub-classes if needed.
+
+        let supportedBefore = this._supported;
+
+        if (this._supports > WI.AuditTestBase.Version) {
+            this.updateSupported(false, options);
+
+            if (options.warn && supportedBefore !== this._supported && Number.isFinite(this._supports))
+                WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run in this Web Inspector").format(this.name));
+        } else if (InspectorBackend.hasDomain("Audit") && this._supports > InspectorBackend.getVersion("Audit")) {
+            this.updateSupported(false, options);
+
+            if (options.warn && supportedBefore !== this._supported && Number.isFinite(this._supports))
+                WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run in the inspected page").format(this.name));
+        } else
+            this.updateSupported(true, options);
+
+        return this._supported;
+    }
+
+    updateSupported(supported, options = {})
+    {
+        // Overridden by sub-classes if needed.
+
+        if (supported === this._supported)
+            return;
+
+        this._supported = supported;
+
+        if (!options.silent)
+            this.dispatchEventToListeners(WI.AuditTestBase.Event.SupportedChanged);
+
+        if (!this._supported)
+            this.clearResult();
+    }
+
+    updateDisabled(disabled, options = {})
+    {
+        // Overridden by sub-classes if needed.
+
+        console.assert(this._runningState === WI.AuditManager.RunningState.Disabled || this._runningState === WI.AuditManager.RunningState.Inactive);
+        if (this._runningState !== WI.AuditManager.RunningState.Disabled && this._runningState !== WI.AuditManager.RunningState.Inactive)
+            return;
+
+        let runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive;
+        if (runningState === this._runningState)
+            return;
+
+        this._runningState = runningState;
+
+        if (!options.silent)
+            this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged);
+
+        if (this.disabled)
+            this.clearResult();
+    }
+
+    updateResult(result)
+    {
+        // Overridden by sub-classes if needed.
+
+        console.assert(result instanceof WI.AuditTestResultBase, result);
+
+        this._result = result;
+
+        this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
+    }
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> // Keep this in sync with Inspector::Protocol::Audit::VERSION.
</span><span class="lines">@@ -237,8 +402,11 @@
</span><span class="cx"> WI.AuditTestBase.Event = {
</span><span class="cx">     Completed: "audit-test-base-completed",
</span><span class="cx">     DisabledChanged: "audit-test-base-disabled-changed",
</span><ins>+    NameChanged: "audit-test-base-name-changed",
</ins><span class="cx">     Progress: "audit-test-base-progress",
</span><span class="cx">     ResultChanged: "audit-test-base-result-changed",
</span><span class="cx">     Scheduled: "audit-test-base-scheduled",
</span><span class="cx">     Stopping: "audit-test-base-stopping",
</span><ins>+    SupportedChanged: "audit-test-base-supported-changed",
+    TestChanged: "audit-test-base-test-changed",
</ins><span class="cx"> };
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestCasejs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js        2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js   2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -27,7 +27,7 @@
</span><span class="cx"> {
</span><span class="cx">     constructor(name, test, options = {})
</span><span class="cx">     {
</span><del>-        console.assert(typeof test === "string");
</del><ins>+        console.assert(typeof test === "string", test);
</ins><span class="cx"> 
</span><span class="cx">         super(name, options);
</span><span class="cx"> 
</span><span class="lines">@@ -77,10 +77,42 @@
</span><span class="cx">         return new WI.AuditTestCase(payload.name, payload.test, options);
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    static stringifyFunction(func, indentLevel)
+    {
+        let string = func.toString();
+
+        // Remove spaces to make the function look unindented.
+        string = string.replaceAll(new RegExp(`^ {${indentLevel}}`, "gm"), "");
+
+        // Replace remaining indentations with the user set indent string.
+        string = string.replaceAll(/^    /gm, WI.indentString());
+
+        return string;
+    }
+
</ins><span class="cx">     // Public
</span><span class="cx"> 
</span><del>-    get test() { return this._test; }
</del><ins>+    get test()
+    {
+        return this._test;
+    }
</ins><span class="cx"> 
</span><ins>+    set test(test)
+    {
+        console.assert(this.editable);
+        console.assert(WI.auditManager.editing);
+        console.assert(typeof test === "string", test);
+
+        if (test === this._test)
+            return;
+
+        this._test = test;
+
+        this.clearResult();
+
+        this.dispatchEventToListeners(WI.AuditTestBase.Event.TestChanged);
+    }
+
</ins><span class="cx">     toJSON(key)
</span><span class="cx">     {
</span><span class="cx">         let json = super.toJSON(key);
</span><span class="lines">@@ -343,9 +375,7 @@
</span><span class="cx">             options.data = data;
</span><span class="cx">         if (resolvedDOMNodes)
</span><span class="cx">             options.resolvedDOMNodes = resolvedDOMNodes;
</span><del>-        this._result = new WI.AuditTestCaseResult(this.name, level, options);
-
-        this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
</del><ins>+        this.updateResult(new WI.AuditTestCaseResult(this.name, level, options));
</ins><span class="cx">     }
</span><span class="cx"> };
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestGroupjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js       2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js  2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -27,7 +27,7 @@
</span><span class="cx"> {
</span><span class="cx">     constructor(name, tests, options = {})
</span><span class="cx">     {
</span><del>-        console.assert(Array.isArray(tests));
</del><ins>+        console.assert(Array.isArray(tests), tests);
</ins><span class="cx"> 
</span><span class="cx">         // Set disabled once `_tests` is set so that it propagates.
</span><span class="cx">         let disabled = options.disabled;
</span><span class="lines">@@ -35,28 +35,12 @@
</span><span class="cx"> 
</span><span class="cx">         super(name, options);
</span><span class="cx"> 
</span><del>-        this._tests = tests;
-        this._preventDisabledPropagation = false;
</del><ins>+        this._tests = [];
+        for (let test of tests)
+            this.addTest(test);
</ins><span class="cx"> 
</span><del>-        if (disabled || !this.supported)
-            this.disabled = true;
-
-        let hasSupportedTest = false;
-
-        for (let test of this._tests) {
-            if (!this.supported)
-                test.supported = false;
-            else if (test.supported)
-                hasSupportedTest = true;
-
-            test.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this);
-            test.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
-            test.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this);
-
-        }
-
-        if (!hasSupportedTest)
-            this.supported = false;
</del><ins>+        if (disabled)
+            this.updateDisabled(true);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     // Static
</span><span class="lines">@@ -121,32 +105,54 @@
</span><span class="cx"> 
</span><span class="cx">     get tests() { return this._tests; }
</span><span class="cx"> 
</span><del>-    get supported()
</del><ins>+    addTest(test)
</ins><span class="cx">     {
</span><del>-        return super.supported;
-    }
</del><ins>+        console.assert(test instanceof WI.AuditTestBase, test);
+        console.assert(!this._tests.includes(test), test);
+        console.assert(!test._parent, test);
</ins><span class="cx"> 
</span><del>-    set supported(supported)
-    {
-        for (let test of this._tests)
-            test.supported = supported;
</del><ins>+        this._tests.push(test);
+        test._parent = this;
</ins><span class="cx"> 
</span><del>-        super.supported = supported;
-    }
</del><ins>+        test.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this);
+        test.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
+        test.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this);
+        if (this.editable) {
+            test.addEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
+            test.addEventListener(WI.AuditTestBase.Event.TestChanged, this._handleTestChanged, this);
+        }
</ins><span class="cx"> 
</span><del>-    get disabled()
-    {
-        return super.disabled;
</del><ins>+        this.dispatchEventToListeners(WI.AuditTestGroup.Event.TestAdded, {test});
+
+        this.determineIfSupported();
+
+        if (this._checkDisabled(test))
+            test.updateDisabled(true, {silent: true});
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    set disabled(disabled)
</del><ins>+    removeTest(test)
</ins><span class="cx">     {
</span><del>-        if (!this._preventDisabledPropagation) {
-            for (let test of this._tests)
-                test.disabled = disabled;
-        }
</del><ins>+        console.assert(this.editable);
+        console.assert(WI.auditManager.editing);
+        console.assert(test instanceof WI.AuditTestBase, test);
+        console.assert(test.editable, test);
+        console.assert(this._tests.includes(test), test);
+        console.assert(test._parent === this, test);
</ins><span class="cx"> 
</span><del>-        super.disabled = disabled;
</del><ins>+        test.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this);
+        test.removeEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
+        test.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this);
+        test.removeEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
+        test.removeEventListener(WI.AuditTestBase.Event.TestChanged, this._handleTestChanged, this);
+
+        this._tests.remove(test);
+        test._parent = null;
+
+        this.dispatchEventToListeners(WI.AuditTestGroup.Event.TestRemoved, {test});
+
+        this.determineIfSupported();
+
+        this._checkDisabled();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     stop()
</span><span class="lines">@@ -161,10 +167,13 @@
</span><span class="cx"> 
</span><span class="cx">     clearResult(options = {})
</span><span class="cx">     {
</span><del>-        let cleared = !!this._result;
-        for (let test of this._tests) {
-            if (test.clearResult(options))
-                cleared = true;
</del><ins>+        let cleared = !!this.result;
+
+        if (!options.excludeTests && this._tests) {
+            for (let test of this._tests) {
+                if (test.clearResult(options))
+                    cleared = true;
+            }
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         return super.clearResult({
</span><span class="lines">@@ -187,7 +196,7 @@
</span><span class="cx">         let count = this._tests.length;
</span><span class="cx">         for (let index = 0; index < count && this._runningState === WI.AuditManager.RunningState.Active; ++index) {
</span><span class="cx">             let test = this._tests[index];
</span><del>-            if (test.disabled)
</del><ins>+            if (test.disabled || !test.supported)
</ins><span class="cx">                 continue;
</span><span class="cx"> 
</span><span class="cx">             await test.start();
</span><span class="lines">@@ -196,22 +205,69 @@
</span><span class="cx">                 this.dispatchEventToListeners(WI.AuditTestBase.Event.Progress, {index, count});
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        this._updateResult();
</del><ins>+        this.updateResult();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-    // Private
</del><ins>+    determineIfSupported(options = {})
+    {
+        if (this._tests) {
+            for (let test of this._tests)
+                test.determineIfSupported({...options, warn: false, silent: true});
+        }
</ins><span class="cx"> 
</span><del>-    _updateResult()
</del><ins>+        return super.determineIfSupported(options);
+    }
+
+    updateSupported(supported, options = {})
</ins><span class="cx">     {
</span><ins>+        if (this._tests && (!supported || this._tests.every((test) => !test.supported))) {
+            supported = false;
+
+            for (let test of this._tests)
+                test.updateSupported(supported, {silent: true});
+        }
+
+        super.updateSupported(supported, options);
+    }
+
+    updateDisabled(disabled, options = {})
+    {
+        if (!options.excludeTests && this._tests) {
+            for (let test of this._tests)
+                test.updateDisabled(disabled, options);
+        }
+
+        super.updateDisabled(disabled, options);
+    }
+
+    updateResult()
+    {
</ins><span class="cx">         let results = this._tests.map((test) => test.result).filter((result) => !!result);
</span><span class="cx">         if (!results.length)
</span><span class="cx">             return;
</span><span class="cx"> 
</span><del>-        this._result = new WI.AuditTestGroupResult(this.name, results, {
</del><ins>+        super.updateResult(new WI.AuditTestGroupResult(this.name, results, {
</ins><span class="cx">             description: this.description,
</span><del>-        });
</del><ins>+        }));
+    }
</ins><span class="cx"> 
</span><del>-        this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
</del><ins>+    // Private
+
+    _checkDisabled(test)
+    {
+        let testDisabled = !test || !test.supported || test.disabled;
+        let enabledTestCount = this._tests.filter((existing) => existing.supported && !existing.disabled).length;
+
+        if (testDisabled && !enabledTestCount)
+            this.updateDisabled(true);
+        else if (!testDisabled && enabledTestCount === 1)
+            this.updateDisabled(false, {excludeTests: true});
+        else {
+            // Don't change `disabled`, as we're currently in an "indeterminate" state.
+            this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged);
+        }
+
+        return this.disabled;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _handleTestCompleted(event)
</span><span class="lines">@@ -219,23 +275,14 @@
</span><span class="cx">         if (this._runningState === WI.AuditManager.RunningState.Active)
</span><span class="cx">             return;
</span><span class="cx"> 
</span><del>-        this._updateResult();
</del><ins>+        this.updateResult();
+
</ins><span class="cx">         this.dispatchEventToListeners(WI.AuditTestBase.Event.Completed);
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _handleTestDisabledChanged(event)
</span><span class="cx">     {
</span><del>-        let enabledTestCount = this._tests.filter((test) => !test.disabled).length;
-        if (event.target.disabled && !enabledTestCount)
-            this.disabled = true;
-        else if (!event.target.disabled && enabledTestCount === 1) {
-            this._preventDisabledPropagation = true;
-            this.disabled = false;
-            this._preventDisabledPropagation = false;
-        } else {
-            // Don't change `disabled`, as we're currently in an "indeterminate" state.
-            this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged);
-        }
</del><ins>+        this._checkDisabled(event.target);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _handleTestProgress(event)
</span><span class="lines">@@ -246,7 +293,7 @@
</span><span class="cx">         let walk = (tests) => {
</span><span class="cx">             let count = 0;
</span><span class="cx">             for (let test of tests) {
</span><del>-                if (test.disabled)
</del><ins>+                if (test.disabled || !test.supported)
</ins><span class="cx">                     continue;
</span><span class="cx"> 
</span><span class="cx">                 if (test instanceof WI.AuditTestCase)
</span><span class="lines">@@ -262,6 +309,25 @@
</span><span class="cx">             count: walk(this._tests),
</span><span class="cx">         });
</span><span class="cx">     }
</span><ins>+
+    _handleTestSupportedChanged(event)
+    {
+        this.determineIfSupported();
+    }
+
+    _handleTestChanged(event)
+    {
+        console.assert(WI.auditManager.editing);
+
+        this.clearResult({excludeTests: true});
+
+        this.dispatchEventToListeners(WI.AuditTestBase.Event.TestChanged);
+    }
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> WI.AuditTestGroup.TypeIdentifier = "test-group";
</span><ins>+
+WI.AuditTestGroup.Event = {
+    TestAdded: "audit-test-group-test-added",
+    TestRemoved: "audit-test-group-test-removed",
+};
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceModelsAuditTestResultBasejs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestResultBase.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestResultBase.js  2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestResultBase.js     2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -69,6 +69,16 @@
</span><span class="cx">         throw WI.NotImplementedError.subclassMustOverride();
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    get disabled()
+    {
+        return false;
+    }
+
+    get editable()
+    {
+        return false;
+    }
+
</ins><span class="cx">     saveIdentityToCookie(cookie)
</span><span class="cx">     {
</span><span class="cx">         cookie["audit-" + this.constructor.TypeIdentifier + "-name"] = this._name;
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditNavigationSidebarPanelcss"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.css (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.css  2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.css     2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -33,19 +33,19 @@
</span><span class="cx">     flex-grow: 1;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled):active {
</del><ins>+.sidebar > .panel.navigation.audit .edit-audits:not(.disabled):active {
</ins><span class="cx">     color: var(--glyph-color-pressed);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated {
</del><ins>+.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated {
</ins><span class="cx">     color: var(--glyph-color-active);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated:active {
</del><ins>+.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated:active {
</ins><span class="cx">     color: var(--glyph-color-active-pressed);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.sidebar > .panel.navigation.audit > .content .edit-audits.disabled {
</del><ins>+.sidebar > .panel.navigation.audit .edit-audits.disabled {
</ins><span class="cx">     color: var(--glyph-color-disabled);
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -58,12 +58,11 @@
</span><span class="cx">     border-bottom: 1px solid var(--border-color);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.finish-editing-audits-placeholder.message-text-view .navigation-item-help .navigation-bar {
-    padding: 0;
-    vertical-align: 0.5px;
</del><ins>+.content-view.audit .message-text-view .navigation-item-help:is(.start-editing-audits, .stop-editing-audits) .navigation-bar {
+    --navigation-item-help-navigation-bar-vertical-align: 0.5px;
</ins><span class="cx"> }
</span><span class="cx"> 
</span><del>-.audit-version {
</del><ins>+.content-view.tab.audit .content-view > .audit-version {
</ins><span class="cx">     position: absolute;
</span><span class="cx">     right: 0;
</span><span class="cx">     bottom: 0;
</span><span class="lines">@@ -74,3 +73,16 @@
</span><span class="cx">     text-align: center;
</span><span class="cx">     color: var(--text-color-secondary);
</span><span class="cx"> }
</span><ins>+
+.content-view.tab.audit .content-view .reference-page-link-container {
+    position: absolute;
+    bottom: 4px;
+}
+
+body[dir=ltr] .content-view.tab.audit .content-view .reference-page-link-container {
+    right: 4px;
+}
+
+body[dir=rtl] .content-view.tab.audit .content-view .reference-page-link-container {
+    left: 4px;
+}
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditNavigationSidebarPaneljs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js   2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js      2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -30,6 +30,13 @@
</span><span class="cx">         super("audit", WI.UIString("Audits"));
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    // Static
+
+    static _createNavigationItemTitle()
+    {
+        return WI.UIString("Create", "Create @ Audit Tab Navigation Sidebar", "Title of button that creates a new audit.");
+    }
+
</ins><span class="cx">     // Public
</span><span class="cx"> 
</span><span class="cx">     showDefaultContentView()
</span><span class="lines">@@ -38,26 +45,53 @@
</span><span class="cx"> 
</span><span class="cx">         if (WI.auditManager.editing) {
</span><span class="cx">             let contentPlaceholder = WI.createMessageTextView(WI.UIString("Editing audits"));
</span><del>-            contentPlaceholder.classList.add("finish-editing-audits-placeholder");
</del><span class="cx">             contentView.element.appendChild(contentPlaceholder);
</span><span class="cx"> 
</span><del>-            let finishEditingNavigationItem = new WI.ButtonNavigationItem("finish-editing-audits", WI.UIString("Done"));
-            finishEditingNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, (event) => {
-                WI.auditManager.editing = false;
-            });
</del><ins>+            let descriptionElement = contentPlaceholder.appendChild(document.createElement("div"));
+            descriptionElement.className = "description";
+            descriptionElement.textContent = WI.UIString("Select an audit in the navigation sidebar to edit it.");
</ins><span class="cx"> 
</span><del>-            let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop editing"), finishEditingNavigationItem);
-            contentPlaceholder.appendChild(importHelpElement);
</del><ins>+            let createAuditNavigationItem = new WI.ButtonNavigationItem("create-audit", WI.AuditNavigationSidebarPanel._createNavigationItemTitle(), "Images/Plus15.svg", 15, 15);
+            createAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+            createAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleCreateButtonNavigationItemClicked, this);
+
+            let createAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to create a new audit."), createAuditNavigationItem);
+            createAuditHelpElement.classList.add("create-audit");
+            contentPlaceholder.appendChild(createAuditHelpElement);
+
+            let stopEditingAuditsNavigationItem = new WI.ButtonNavigationItem("stop-editing-audits", WI.UIString("Done"));
+            stopEditingAuditsNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleEditButtonNavigationItemClicked, this);
+
+            let stopEditingAuditsHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop editing audits."), stopEditingAuditsNavigationItem);
+            stopEditingAuditsHelpElement.classList.add("stop-editing-audits");
+            contentPlaceholder.appendChild(stopEditingAuditsHelpElement);
</ins><span class="cx">         } else {
</span><ins>+            let hasEnabledAudit = WI.auditManager.tests.length && WI.auditManager.tests.some((test) => !test.disabled && test.supported);
+
</ins><span class="cx">             let contentPlaceholder = WI.createMessageTextView(WI.UIString("No audit selected"));
</span><span class="cx">             contentView.element.appendChild(contentPlaceholder);
</span><span class="cx"> 
</span><del>-            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, this._handleImportButtonNavigationItemClicked, this);
</del><ins>+            if (hasEnabledAudit) {
+                let descriptionElement = contentPlaceholder.appendChild(document.createElement("div"));
+                descriptionElement.className = "description";
+                descriptionElement.textContent = WI.UIString("Select an audit in the navigation sidebar to view its results.");
+            }
</ins><span class="cx"> 
</span><del>-            let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to import a test or result file"), importNavigationItem);
-            contentPlaceholder.appendChild(importHelpElement);
</del><ins>+            let importAuditNavigationItem = new WI.ButtonNavigationItem("import-audit", WI.UIString("Import"), "Images/Import.svg", 15, 15);
+            importAuditNavigationItem.title = WI.UIString("Import audit or result");
+            importAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+            importAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
+
+            let importAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to import an audit or a result."), importAuditNavigationItem);
+            importAuditHelpElement.classList.add("import-audit");
+            contentPlaceholder.appendChild(importAuditHelpElement);
+
+            let startEditingAuditsNavigationItem = new WI.ButtonNavigationItem("start-editing-audits", WI.UIString("Edit"));
+            startEditingAuditsNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleEditButtonNavigationItemClicked, this);
+
+            let startEditingAuditsHelpElement = WI.createNavigationItemHelp(hasEnabledAudit ? WI.UIString("Press %s to start editing audits.") : WI.UIString("Press %s to enable audits."), startEditingAuditsNavigationItem);
+            startEditingAuditsHelpElement.classList.add("start-editing-audits");
+            contentPlaceholder.appendChild(startEditingAuditsHelpElement);
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         let versionContainer = contentView.element.appendChild(document.createElement("div"));
</span><span class="lines">@@ -68,9 +102,28 @@
</span><span class="cx">             version = Math.min(version, InspectorBackend.getVersion("Audit"));
</span><span class="cx">         versionContainer.textContent = WI.UIString("Audit version: %s").format(version);
</span><span class="cx"> 
</span><ins>+        versionContainer.appendChild(WI.createReferencePageLink("audit-tab"));
+
</ins><span class="cx">         this.contentBrowser.showContentView(contentView);
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    // Popover delegate
+
+    willDismissPopover(popover)
+    {
+        console.assert(popover instanceof WI.CreateAuditPopover, popover);
+
+        let audit = popover.audit;
+        if (!audit) {
+            InspectorFrontendHost.beep();
+            return;
+        }
+
+        WI.auditManager.addTest(audit);
+
+        WI.showRepresentedObject(audit);
+    }
+
</ins><span class="cx">     // Protected
</span><span class="cx"> 
</span><span class="cx">     initialLayout()
</span><span class="lines">@@ -81,30 +134,30 @@
</span><span class="cx"> 
</span><span class="cx">         let controlsNavigationBar = new WI.NavigationBar;
</span><span class="cx"> 
</span><del>-        this._startStopButtonNavigationItem = new WI.ToggleButtonNavigationItem("audit-start-stop", WI.UIString("Start"), WI.UIString("Stop"), "Images/AuditStart.svg", "Images/AuditStop.svg", 13, 13);
</del><ins>+        this._startStopButtonNavigationItem = new WI.ToggleButtonNavigationItem("start-stop-audit", WI.UIString("Start"), WI.UIString("Stop"), "Images/AuditStart.svg", "Images/AuditStop.svg", 13, 13);
</ins><span class="cx">         this._startStopButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
</span><del>-        this._updateStartStopButtonNavigationItemState();
</del><span class="cx">         this._startStopButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleStartStopButtonNavigationItemClicked, this);
</span><span class="cx">         controlsNavigationBar.addNavigationItem(this._startStopButtonNavigationItem);
</span><span class="cx"> 
</span><ins>+        this._createButtonNavigationItem = new WI.ButtonNavigationItem("create-audit", WI.AuditNavigationSidebarPanel._createNavigationItemTitle(), "Images/Plus15.svg", 15, 15);
+        this._createButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+        this._createButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleCreateButtonNavigationItemClicked, this);
+        controlsNavigationBar.addNavigationItem(this._createButtonNavigationItem);
+
</ins><span class="cx">         controlsNavigationBar.addNavigationItem(new WI.DividerNavigationItem);
</span><span class="cx"> 
</span><del>-        let importButtonNavigationItem = new WI.ButtonNavigationItem("audit-import", WI.UIString("Import"), "Images/Import.svg", 15, 15);
</del><ins>+        let importButtonNavigationItem = new WI.ButtonNavigationItem("import-audit", WI.UIString("Import"), "Images/Import.svg", 15, 15);
+        importButtonNavigationItem.title = WI.UIString("Import audit or result");
</ins><span class="cx">         importButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
</span><del>-        importButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
</del><span class="cx">         importButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
</span><span class="cx">         controlsNavigationBar.addNavigationItem(importButtonNavigationItem);
</span><span class="cx"> 
</span><span class="cx">         this.addSubview(controlsNavigationBar);
</span><span class="cx"> 
</span><del>-        let editNavigationbar = new WI.NavigationBar;
-
</del><span class="cx">         this._editButtonNavigationItem = new WI.ActivateButtonNavigationItem("edit-audits", WI.UIString("Edit"), WI.UIString("Done"));
</span><span class="cx">         this._editButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleEditButtonNavigationItemClicked, this);
</span><del>-        editNavigationbar.addNavigationItem(this._editButtonNavigationItem);
</del><ins>+        this.filterBar.addFilterNavigationItem(this._editButtonNavigationItem);
</ins><span class="cx"> 
</span><del>-        this.contentView.addSubview(editNavigationbar);
-
</del><span class="cx">         for (let test of WI.auditManager.tests)
</span><span class="cx">             this._addTest(test);
</span><span class="cx"> 
</span><span class="lines">@@ -112,7 +165,14 @@
</span><span class="cx">             this._addResult(result, i);
</span><span class="cx">         });
</span><span class="cx"> 
</span><ins>+        this._updateControlNavigationItems();
+        this._updateEditNavigationItems();
+        this._updateNoAuditsPlaceholder();
+
+        WI.AuditTestGroup.addEventListener(WI.AuditTestGroup.Event.TestRemoved, this._handleAuditTestRemoved, this);
+
</ins><span class="cx">         WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleAuditManagerEditingChanged, this);
</span><ins>+        WI.auditManager.addEventListener(WI.AuditManager.Event.RunningStateChanged, this._handleAuditManagerRunningStateChanged, this);
</ins><span class="cx">         WI.auditManager.addEventListener(WI.AuditManager.Event.TestAdded, this._handleAuditTestAdded, this);
</span><span class="cx">         WI.auditManager.addEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditTestCompleted, this);
</span><span class="cx">         WI.auditManager.addEventListener(WI.AuditManager.Event.TestRemoved, this._handleAuditTestRemoved, this);
</span><span class="lines">@@ -147,7 +207,7 @@
</span><span class="cx">             if (treeElement.representedObject instanceof WI.AuditTestResultBase || treeElement.hasAncestor(this._resultsFolderTreeElement) || treeElement === this._resultsFolderTreeElement)
</span><span class="cx">                 return false;
</span><span class="cx">         } else {
</span><del>-            if (treeElement.representedObject instanceof WI.AuditTestBase && treeElement.representedObject.disabled)
</del><ins>+            if (treeElement.representedObject instanceof WI.AuditTestBase && (treeElement.representedObject.disabled || !treeElement.representedObject.supported))
</ins><span class="cx">                 return false;
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="lines">@@ -165,10 +225,6 @@
</span><span class="cx">             this._resultsFolderTreeElement.hidden = !this._resultsFolderTreeElement.children.length;
</span><span class="cx">         } else
</span><span class="cx">             this.contentTreeOutline.appendChild(treeElement);
</span><del>-
-        this._updateStartStopButtonNavigationItemState();
-        this._updateEditButtonNavigationItemState();
-        this._updateNoAuditsPlaceholder();
</del><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _addResult(result, index)
</span><span class="lines">@@ -193,27 +249,27 @@
</span><span class="cx"> 
</span><span class="cx">         for (let resultItem of result)
</span><span class="cx">             resultFolderTreeElement.appendChild(new WI.AuditTreeElement(resultItem));
</span><del>-
-        this._updateStartStopButtonNavigationItemState();
-        this._updateEditButtonNavigationItemState();
</del><span class="cx">     }
</span><span class="cx"> 
</span><del>-    _updateStartStopButtonNavigationItemState()
</del><ins>+    _updateControlNavigationItems()
</ins><span class="cx">     {
</span><span class="cx">         this._startStopButtonNavigationItem.toggled = WI.auditManager.runningState === WI.AuditManager.RunningState.Active || WI.auditManager.runningState === WI.AuditManager.RunningState.Stopping;
</span><del>-        this._startStopButtonNavigationItem.enabled = WI.auditManager.tests.some((test) => !test.disabled) && (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive || WI.auditManager.runningState === WI.AuditManager.RunningState.Active);
</del><ins>+        this._startStopButtonNavigationItem.enabled = WI.auditManager.tests.some((test) => !test.disabled && test.supported) && (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive || WI.auditManager.runningState === WI.AuditManager.RunningState.Active);
+        this._startStopButtonNavigationItem.hidden = WI.auditManager.editing;
+
+        this._createButtonNavigationItem.hidden = !WI.auditManager.editing;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><del>-     _updateEditButtonNavigationItemState()
</del><ins>+     _updateEditNavigationItems()
</ins><span class="cx">     {
</span><span class="cx">         this._editButtonNavigationItem.label = WI.auditManager.editing ? this._editButtonNavigationItem.activatedToolTip : this._editButtonNavigationItem.defaultToolTip;
</span><span class="cx">         this._editButtonNavigationItem.activated = WI.auditManager.editing;
</span><del>-        this._editButtonNavigationItem.enabled = WI.auditManager.tests.length && (WI.auditManager.editing || WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive);
</del><ins>+        this._editButtonNavigationItem.enabled = WI.auditManager.editing || WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _updateNoAuditsPlaceholder()
</span><span class="cx">     {
</span><del>-        if (WI.auditManager.editing || WI.auditManager.tests.some((test) => !test.disabled)) {
</del><ins>+        if (WI.auditManager.editing || WI.auditManager.tests.some((test) => !test.disabled && test.supported)) {
</ins><span class="cx">             if (!this.hasActiveFilters)
</span><span class="cx">                 this.hideEmptyContentPlaceholder();
</span><span class="cx">             return;
</span><span class="lines">@@ -231,36 +287,41 @@
</span><span class="cx"> 
</span><span class="cx">     _handleAuditManagerEditingChanged(event)
</span><span class="cx">     {
</span><del>-        if (WI.auditManager.editing) {
-            console.assert(!this._selectedTreeElementBeforeEditing);
-            this._selectedTreeElementBeforeEditing = this.contentTreeOutline.selectedTreeElement;
-            if (this._selectedTreeElementBeforeEditing)
-                this._selectedTreeElementBeforeEditing.deselect();
-        } else if (this._selectedTreeElementBeforeEditing) {
-            if (this.contentTreeOutline.selectedTreeElement === this._selectedTreeElementBeforeEditing) {
-                const suppressNotification = true;
-                this._selectedTreeElementBeforeEditing.deselect(suppressNotification);
</del><ins>+        let previousSelectedTreeElement = this.contentTreeOutline.selectedTreeElement;
+        if (previousSelectedTreeElement) {
+            if (WI.auditManager.editing) {
+                if (!(previousSelectedTreeElement.representedObject instanceof WI.AuditTestBase))
+                    previousSelectedTreeElement.deselect();
+            } else {
+                if (previousSelectedTreeElement.representedObject.disabled || !previousSelectedTreeElement.representedObject.supported)
+                    previousSelectedTreeElement.deselect();
</ins><span class="cx">             }
</span><del>-            if (!(this._selectedTreeElementBeforeEditing.representedObject instanceof WI.AuditTestBase) || !this._selectedTreeElementBeforeEditing.representedObject.disabled) {
-                const omitFocus = false;
-                const selectedByUser = true;
-                this._selectedTreeElementBeforeEditing.select(omitFocus, selectedByUser);
-            }
-            this._selectedTreeElementBeforeEditing = null;
</del><span class="cx">         }
</span><span class="cx"> 
</span><ins>+        this.updateFilter();
+
</ins><span class="cx">         if (!this.contentTreeOutline.selectedTreeElement)
</span><span class="cx">             this.showDefaultContentView();
</span><span class="cx"> 
</span><del>-        this._updateStartStopButtonNavigationItemState();
-        this._updateEditButtonNavigationItemState();
</del><ins>+        this._updateControlNavigationItems();
+        this._updateEditNavigationItems();
+        this._updateNoAuditsPlaceholder();
+    }
</ins><span class="cx"> 
</span><del>-        this.updateFilter();
</del><ins>+    _handleAuditManagerRunningStateChanged(event)
+    {
+        this._updateControlNavigationItems();
+        this._updateEditNavigationItems();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _handleAuditTestAdded(event)
</span><span class="cx">     {
</span><del>-        this._addTest(event.data.test);
</del><ins>+        let {test} = event.data;
+
+        this._addTest(test);
+
+        this._updateControlNavigationItems();
+        this._updateNoAuditsPlaceholder();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _handleAuditTestCompleted(event)
</span><span class="lines">@@ -267,23 +328,27 @@
</span><span class="cx">     {
</span><span class="cx">         let {result, index} = event.data;
</span><span class="cx">         this._addResult(result, index);
</span><ins>+
+        this._updateControlNavigationItems();
+        this._updateEditNavigationItems();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _handleAuditTestRemoved(event)
</span><span class="cx">     {
</span><ins>+        console.assert(WI.auditManager.editing);
+
</ins><span class="cx">         let {test} = event.data;
</span><ins>+
</ins><span class="cx">         let treeElement = this.treeElementForRepresentedObject(test);
</span><del>-        this.contentTreeOutline.removeChild(treeElement);
</del><ins>+        treeElement.parent.removeChild(treeElement);
</ins><span class="cx"> 
</span><del>-        this._updateStartStopButtonNavigationItemState();
-        this._updateEditButtonNavigationItemState();
-        this._updateNoAuditsPlaceholder();
</del><ins>+        this._updateControlNavigationItems();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _handleAuditTestScheduled(event)
</span><span class="cx">     {
</span><del>-        this._updateStartStopButtonNavigationItemState();
-        this._updateEditButtonNavigationItemState();
</del><ins>+        this._updateControlNavigationItems();
+        this._updateEditNavigationItems();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _treeSelectionDidChange(event)
</span><span class="lines">@@ -297,9 +362,6 @@
</span><span class="cx">             return;
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        if (WI.auditManager.editing)
-            return;
-
</del><span class="cx">         let representedObject = treeElement.representedObject;
</span><span class="cx">         if (representedObject instanceof WI.AuditTestCase || representedObject instanceof WI.AuditTestGroup
</span><span class="cx">             || representedObject instanceof WI.AuditTestCaseResult || representedObject instanceof WI.AuditTestGroupResult) {
</span><span class="lines">@@ -316,8 +378,14 @@
</span><span class="cx">             WI.auditManager.start();
</span><span class="cx">         else if (WI.auditManager.runningState === WI.AuditManager.RunningState.Active)
</span><span class="cx">             WI.auditManager.stop();
</span><ins>+    }
</ins><span class="cx"> 
</span><del>-        this._updateStartStopButtonNavigationItemState();
</del><ins>+    _handleCreateButtonNavigationItemClicked(event)
+    {
+        console.assert(WI.auditManager.editing);
+
+        let popover = new WI.CreateAuditPopover(this);
+        popover.show(event.target.element, [WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X, WI.RectEdge.MIN_X]);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _handleImportButtonNavigationItemClicked(event)
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTabContentViewjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTabContentView.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTabContentView.js   2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTabContentView.js      2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -29,6 +29,7 @@
</span><span class="cx">     {
</span><span class="cx">         super(AuditTabContentView.tabInfo(), {
</span><span class="cx">             navigationSidebarPanelConstructor: WI.AuditNavigationSidebarPanel,
</span><ins>+            disableBackForward: true,
</ins><span class="cx">         });
</span><span class="cx"> 
</span><span class="cx">         this._startStopShortcut = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.Space, this._handleSpace.bind(this));
</span><span class="lines">@@ -111,7 +112,7 @@
</span><span class="cx">         super.initialLayout();
</span><span class="cx"> 
</span><span class="cx">         let dropZoneView = new WI.DropZoneView(this);
</span><del>-        dropZoneView.text = WI.UIString("Import Audit");
</del><ins>+        dropZoneView.text = WI.UIString("Import Audit or Result");
</ins><span class="cx">         dropZoneView.targetElement = this.element;
</span><span class="cx">         this.addSubview(dropZoneView);
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestCaseContentViewcss"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css     2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css        2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -23,20 +23,44 @@
</span><span class="cx">  * THE POSSIBILITY OF SUCH DAMAGE.
</span><span class="cx">  */
</span><span class="cx"> 
</span><ins>+.content-view-container > .content-view.audit-test-case {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    overflow: hidden;
+}
+
</ins><span class="cx"> .content-view-container > .content-view.audit-test-case > header {
</span><ins>+    flex-shrink: 0;
</ins><span class="cx">     position: sticky;
</span><del>-    top: -1px;
</del><ins>+    top: 0;
</ins><span class="cx">     z-index: var(--z-index-header);
</span><del>-    margin-top: -1px;
</del><span class="cx">     background-color: var(--audit-test-header-background-color);
</span><del>-    border-top: 1px solid var(--border-color);
</del><span class="cx">     -webkit-backdrop-filter: blur(20px);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view):first-child {
</del><ins>+.content-view-container > .content-view.audit-test-case.manager-editing > header h1 > img {
+    width: 0.75em;
+    min-width: 12px;
+    height: 0.75em;
+    min-height: 12px;
+    margin: 0.125em;
+    margin-inline-end: 0.375em;
+}
+
+.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view, .editor):first-child {
</ins><span class="cx">     margin-top: var(--audit-test-vertical-space);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.content-view-container > .content-view.audit-test-case > section {
+    overflow-y: scroll;
+}
+
+.content-view-container > .content-view.audit-test-case > section,
+.content-view-container > .content-view.audit-test-case > section :is(.editor, .CodeMirror) {
+    height: 100%;
+}
+
</ins><span class="cx"> .content-view.audit-test-case > header {
</span><span class="cx">     -webkit-padding-end: calc(var(--audit-test-horizontal-space) / 2);
</span><span class="cx"> }
</span><span class="lines">@@ -54,6 +78,11 @@
</span><span class="cx">     -webkit-margin-end: 0.25em;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.content-view.audit-test-case.manager-editing.disabled:not(.editable) > header h1 > img {
+    opacity: 0.3;
+    pointer-events: none;
+}
+
</ins><span class="cx"> .content-view.audit-test-case > header > .metadata {
</span><span class="cx">     display: flex;
</span><span class="cx">     align-items: center;
</span><span class="lines">@@ -83,16 +112,16 @@
</span><span class="cx">     font-weight: bold;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.content-view.audit-test-case > section > :not(.message-text-view) {
</del><ins>+.content-view.audit-test-case > section > :not(.message-text-view, .editor) {
</ins><span class="cx">     margin-right: var(--audit-test-horizontal-space);
</span><span class="cx">     margin-left: var(--audit-test-horizontal-space);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.content-view.audit-test-case > section > :not(.message-text-view):last-child {
</del><ins>+.content-view.audit-test-case > section > :not(.message-text-view, .editor):last-child {
</ins><span class="cx">     margin-bottom: var(--audit-test-vertical-space);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.content-view.audit-test-case > section > :not(.message-text-view) + :not(.message-text-view) {
</del><ins>+.content-view.audit-test-case > section > :not(.message-text-view, .editor) + :not(.message-text-view, .editor) {
</ins><span class="cx">     margin-top: var(--audit-test-vertical-space);
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="lines">@@ -131,12 +160,13 @@
</span><span class="cx">     width: 100%;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.content-view.audit-test-case > section .CodeMirror {
-    width: 100%;
-    height: auto;
-}
-
</del><span class="cx"> .content-view.audit-test-case > section .mark {
</span><span class="cx">     background-color: hsla(53, 83%, 53%, 0.2);
</span><span class="cx">     border-bottom: 1px solid hsl(47, 82%, 60%);
</span><span class="cx"> }
</span><ins>+
+@media (prefers-color-scheme: dark) {
+    .content-view.audit-test-case.manager-editing > header h1 > img {
+        filter: var(--filter-invert);
+    }
+}
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestCaseContentViewjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js      2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js 2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -47,17 +47,17 @@
</span><span class="cx">         let informationContainer = this.headerView.element.appendChild(document.createElement("div"));
</span><span class="cx">         informationContainer.classList.add("information");
</span><span class="cx"> 
</span><del>-        let nameElement = informationContainer.appendChild(document.createElement("h1"));
</del><ins>+        let nameContainer = informationContainer.appendChild(document.createElement("h1"));
</ins><span class="cx"> 
</span><del>-        this._resultImageElement = nameElement.appendChild(document.createElement("img"));
</del><ins>+        this._resultImageElement = nameContainer.appendChild(document.createElement("img"));
</ins><span class="cx"> 
</span><del>-        nameElement.appendChild(document.createTextNode(this.representedObject.name));
</del><ins>+        nameContainer.appendChild(this.createNameElement("span"));
</ins><span class="cx"> 
</span><del>-        if (this.representedObject.description) {
-            let descriptionElement = informationContainer.appendChild(document.createElement("p"));
-            descriptionElement.textContent = this.representedObject.description;
-        }
</del><ins>+        informationContainer.appendChild(this.createDescriptionElement("p"));
</ins><span class="cx"> 
</span><ins>+        if (this.representedObject instanceof WI.AuditTestCase)
+            informationContainer.appendChild(this.createControlsTableElement());
+
</ins><span class="cx">         this._metadataElement = this.headerView.element.appendChild(document.createElement("div"));
</span><span class="cx">         this._metadataElement.classList.add("metadata");
</span><span class="cx">     }
</span><span class="lines">@@ -69,11 +69,41 @@
</span><span class="cx"> 
</span><span class="cx">         super.layout();
</span><span class="cx"> 
</span><del>-        this._resultImageElement.src = "Images/AuditTestNoResult.svg";
</del><span class="cx">         this._metadataElement.removeChildren();
</span><del>-
</del><span class="cx">         this.contentView.element.removeChildren();
</span><span class="cx"> 
</span><ins>+        if (WI.auditManager.editing) {
+            this._resultImageElement.src = "Images/Pencil.svg";
+            this._resultImageElement.title = WI.UIString("Editing audit", "Editing Audit @ Audit Tab - Test Case", "Title of icon indiciating that the selected audit is being edited.");
+
+            let testEditorElement = this.contentView.element.appendChild(document.createElement("div"));
+            testEditorElement.className = "editor";
+
+            // Give the rest of the view a chance to load.
+            setTimeout(() => {
+                let testCodeMirror = WI.CodeMirrorEditor.create(testEditorElement, {
+                    autoCloseBrackets: true,
+                    lineNumbers: true,
+                    lineWrapping: true,
+                    matchBrackets: true,
+                    mode: "text/javascript",
+                    readOnly: this.representedObject.editable ? false : "nocursor",
+                    styleSelectedText: true,
+                    value: this.representedObject.test,
+                });
+
+                if (this.representedObject.editable) {
+                    testCodeMirror.on("blur", (event) => {
+                        this.representedObject.test = testCodeMirror.getValue().trim();
+                    });
+                }
+            });
+            return;
+        }
+
+        this._resultImageElement.src = "Images/AuditTestNoResult.svg";
+        this._resultImageElement.title = WI.UIString("Not yet run", "Not yet run @ Audit Tab - Test Case", "Title of icon indicating that the selected audit has not been run yet.");
+
</ins><span class="cx">         let result = this.representedObject.result;
</span><span class="cx">         if (!result) {
</span><span class="cx">             if (this.representedObject.runningState === WI.AuditManager.RunningState.Inactive)
</span><span class="lines">@@ -86,16 +116,22 @@
</span><span class="cx">             return;
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        if (result.didError)
</del><ins>+        if (result.didError) {
</ins><span class="cx">             this._resultImageElement.src = "Images/AuditTestError.svg";
</span><del>-        else if (result.didFail)
</del><ins>+            this._resultImageElement.title = WI.UIString("Error", "Error @ Audit Tab - Test Case", "Title of icon indicating that the selected audit threw an error.");
+        } else if (result.didFail) {
</ins><span class="cx">             this._resultImageElement.src = "Images/AuditTestFail.svg";
</span><del>-        else if (result.didWarn)
</del><ins>+            this._resultImageElement.title = WI.UIString("Fail", "Fail @ Audit Tab - Test Case", "Title of icon indicating that the selected audit failed.");
+        } else if (result.didWarn) {
</ins><span class="cx">             this._resultImageElement.src = "Images/AuditTestWarn.svg";
</span><del>-        else if (result.didPass)
</del><ins>+            this._resultImageElement.title = WI.UIString("Warn", "Warn @ Audit Tab - Test Case", "Title of icon indicating that the selected audit passed with issues (i.e. warnings).");
+        } else if (result.didPass) {
</ins><span class="cx">             this._resultImageElement.src = "Images/AuditTestPass.svg";
</span><del>-        else if (result.unsupported)
</del><ins>+            this._resultImageElement.title = WI.UIString("Pass", "Pass @ Audit Tab - Test Case", "Title of icon indicating that the selected audit passed with no issues.");
+        } else if (result.unsupported) {
</ins><span class="cx">             this._resultImageElement.src = "Images/AuditTestUnsupported.svg";
</span><ins>+            this._resultImageElement.title = WI.UIString("Unsupported", "Unsupported @ Audit Tab - Test Case", "Title of icon indicating that the selected audit is not able to be run (i.e. unsupported).");
+        }
</ins><span class="cx"> 
</span><span class="cx">         let metadata = result.metadata;
</span><span class="cx">         if (metadata) {
</span><span class="lines">@@ -299,6 +335,17 @@
</span><span class="cx"> 
</span><span class="cx">             let spinner = new WI.IndeterminateProgressSpinner;
</span><span class="cx">             this.placeholderElement.appendChild(spinner.element);
</span><ins>+
+            let stopAuditNavigationItem = new WI.ButtonNavigationItem("stop-audit", WI.UIString("Stop"), "Images/AuditStop.svg", 13, 13);
+            stopAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+            stopAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, (event) => {
+                WI.auditManager.stop();
+            }, WI.auditManager);
+
+            let stopAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop running."), stopAuditNavigationItem);
+            this.placeholderElement.appendChild(stopAuditHelpElement);
+
+            this.placeholderElement.appendChild(WI.createReferencePageLink("audit-tab"));
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         super.showRunningPlaceholder();
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestContentViewcss"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.css (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.css 2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.css    2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -48,10 +48,27 @@
</span><span class="cx">     --audit-test-header-background-color: hsla(0, 0%, 98%, 0.7);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.content-view.audit-test:is(.unsupported, .disabled):not(.manager-editing) {
+    display: none;
+}
+
</ins><span class="cx"> .content-view.audit-test h1 {
</span><span class="cx">     margin: 0;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.content-view.audit-test.manager-editing .editor:not(:empty) {
+    width: 100%;
+}
+
+.content-view.audit-test.manager-editing :is(.content-view.audit-test, header) .editor:not(:empty) {
+    border: 1px solid var(--border-color);
+}
+
+.content-view.audit-test .CodeMirror {
+    width: 100%;
+    height: auto;
+}
+
</ins><span class="cx"> .content-view.audit-test > header {
</span><span class="cx">     display: flex;
</span><span class="cx">     align-items: center;
</span><span class="lines">@@ -70,6 +87,69 @@
</span><span class="cx">     margin: 4px 0 0;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.content-view.audit-test > header :is(.name, .description):not([contenteditable]) {
+    padding: 1px;
+    outline: none;
+}
+
+.content-view.audit-test.manager-editing > header :is(.name, .description)[contenteditable] {
+    border: 1px solid var(--border-color);
+    -webkit-user-select: text;
+    outline-offset: var(--focus-ring-outline-offset);
+}
+
+.content-view.audit-test.manager-editing > header .name[contenteditable]:empty {
+    border-color: var(--error-text-color);
+}
+
+.content-view.audit-test.manager-editing > header .name[contenteditable]:empty:before {
+    content: attr(data-name);
+    color: var(--text-color-tertiary);
+}
+
+.content-view.audit-test.manager-editing > header .description[contenteditable]:empty:before {
+    content: "description";
+    color: var(--text-color-tertiary);
+}
+
+.content-view.audit-test:not(.manager-editing) > header .description:empty,
+.content-view.audit-test:not(.manager-editing) > header table.controls {
+    display: none;
+}
+
+.content-view.audit-test > header table.controls,
+.content-view.audit-test > header table.controls > tr > td {
+    width: 100%;
+}
+
+.content-view.audit-test > header table.controls > tr > th {
+    text-align: end;
+    vertical-align: top;
+    line-height: 2em;
+    color: hsl(0, 0%, 34%);
+}
+
+.content-view.audit-test > header table.controls > tr.supports input[type="number"] {
+    text-align: end;
+}
+
+.content-view.audit-test > header table.controls > tr.supports .warning {
+    margin-inline-start: 4px;
+    color: var(--text-color-secondary);
+}
+
+.content-view.audit-test > header table.controls > tr.supports .warning:not(:empty)::before {
+    display: inline-block;
+    width: 1em;
+    margin-inline-end: 2px;
+    vertical-align: -1px;
+    content: url(../Images/Warning.svg);
+}
+
+.content-view.audit-test > header table.controls > tr.setup .editor {
+    margin-top: 2px;
+}
+
</ins><span class="cx"> .content-view.audit-test .audit-test.filtered,
</span><span class="cx"> .content-view.audit-test .audit-test .message-text-view {
</span><span class="cx">     display: none;
</span><span class="lines">@@ -83,6 +163,10 @@
</span><span class="cx">     background-color: var(--background-color-content);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.content-view.audit-test > section > .message-text-view > :is(progress, .indeterminate-progress-spinner) {
+    margin-bottom: 8px;
+}
+
</ins><span class="cx"> .content-view.audit-test.showing-placeholder {
</span><span class="cx">     display: flex;
</span><span class="cx">     flex-direction: column;
</span><span class="lines">@@ -100,4 +184,8 @@
</span><span class="cx">     .content-view.audit-test {
</span><span class="cx">         --audit-test-header-background-color: hsla(0, 0%, 23%, 0.7);
</span><span class="cx">     }
</span><ins>+
+    .content-view.audit-test > header table.controls > tr > th {
+        color: var(--text-color-secondary);
+    }
</ins><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestContentViewjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js  2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js     2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -35,18 +35,36 @@
</span><span class="cx">         console.assert(this.constructor !== WI.AuditTestContentView && this instanceof WI.AuditTestContentView);
</span><span class="cx"> 
</span><span class="cx">         this.element.classList.add("audit-test");
</span><ins>+        if (this.representedObject.editable)
+            this.element.classList.add("editable");
</ins><span class="cx"> 
</span><del>-        this._exportButtonNavigationItem = new WI.ButtonNavigationItem("audit-export", WI.UIString("Export"), "Images/Export.svg", 15, 15);
-        this._exportButtonNavigationItem.tooltip = WI.UIString("Export result (%s)").format(WI.saveKeyboardShortcut.displayName);
-        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();
</del><ins>+        if (this.representedObject instanceof WI.AuditTestBase) {
+            this._exportTestButtonNavigationItem = new WI.ButtonNavigationItem("audit-export-test", WI.UIString("Export Audit"), "Images/Export.svg", 15, 15);
+            this._exportTestButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+            this._exportTestButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
+            this._exportTestButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleExportTestButtonNavigationItemClicked, this);
+        }
</ins><span class="cx"> 
</span><ins>+        this._exportResultButtonNavigationItem = new WI.ButtonNavigationItem("audit-export-result", WI.UIString("Export Result"), "Images/Export.svg", 15, 15);
+        this._exportResultButtonNavigationItem.tooltip = WI.UIString("Export result (%s)", "Export result (%s) @ Audit Tab", "Tooltip for button that exports the most recent result after running an audit.").format(WI.saveKeyboardShortcut.displayName);
+        this._exportResultButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+        this._exportResultButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
+        this._exportResultButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleExportResultButtonNavigationItemClicked, this);
+
+        this._updateExportNavigationItems();
+
</ins><span class="cx">         this._headerView = new WI.View(document.createElement("header"));
</span><span class="cx">         this._contentView = new WI.View(document.createElement("section"));
</span><span class="cx">         this._placeholderElement = null;
</span><span class="cx"> 
</span><ins>+        this._cachedName = this.representedObject.name;
+        this._nameElement = null;
+
+        this._descriptionElement = null;
+
+        this._supportsInputElement = null;
+        this._supportsWarningElement = null;
+
</ins><span class="cx">         this._shownResult = null;
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="lines">@@ -54,7 +72,11 @@
</span><span class="cx"> 
</span><span class="cx">     get navigationItems()
</span><span class="cx">     {
</span><del>-        return [this._exportButtonNavigationItem];
</del><ins>+        let navigationItems = [];
+        if (this._exportTestButtonNavigationItem)
+            navigationItems.push(this._exportTestButtonNavigationItem);
+        navigationItems.push(this._exportResultButtonNavigationItem);
+        return navigationItems;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     // Protected
</span><span class="lines">@@ -64,7 +86,7 @@
</span><span class="cx"> 
</span><span class="cx">     get supportsSave()
</span><span class="cx">     {
</span><del>-        return !!this.representedObject.result;
</del><ins>+        return !WI.auditManager.editing && !!this.representedObject.result;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     get saveData()
</span><span class="lines">@@ -79,6 +101,123 @@
</span><span class="cx">         return this.representedObject.result;
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    createNameElement(tagName)
+    {
+        console.assert(!this._nameElement);
+
+        this._nameElement = document.createElement(tagName);
+        this._nameElement.textContent = this.representedObject.name;
+        this._nameElement.className = "name";
+
+        if (this.representedObject.editable) {
+            this._nameElement.spellcheck = false;
+
+            this._nameElement.addEventListener("keydown", (event) => {
+                this._handleEditorKeydown(event, this._descriptionElement);
+            });
+
+            this._nameElement.addEventListener("input", (event) => {
+                console.assert(WI.auditManager.editing);
+
+                let name = this._nameElement.textContent;
+                if (!name.trim()) {
+                    name = this._cachedName;
+                    this._nameElement.removeChildren();
+                }
+                this.representedObject.name = name;
+            });
+        }
+
+        return this._nameElement;
+    }
+
+    createDescriptionElement(tagName)
+    {
+        console.assert(!this._descriptionElement);
+
+        this._descriptionElement = document.createElement(tagName);
+        this._descriptionElement.textContent = this.representedObject.description;
+        this._descriptionElement.className = "description";
+
+        if (this.representedObject.editable) {
+            this._descriptionElement.spellcheck = false;
+
+            this._descriptionElement.addEventListener("keydown", (event) => {
+                this._handleEditorKeydown(event, this._supportsInputElement);
+            });
+
+            this._descriptionElement.addEventListener("input", (event) => {
+                console.assert(WI.auditManager.editing);
+
+                let description = this._descriptionElement.textContent;
+                if (!description.trim()) {
+                    description = "";
+                    this._descriptionElement.removeChildren();
+                }
+                this.representedObject.description = description;
+            });
+        }
+
+        return this._descriptionElement;
+    }
+
+    createControlsTableElement()
+    {
+        console.assert(this.representedObject instanceof WI.AuditTestBase);
+        console.assert(!this._supportsInputElement);
+        console.assert(!this._supportsWarningElement);
+
+        let controlsTableElement = document.createElement("table");
+        controlsTableElement.className = "controls";
+
+        let supportsRowElement = controlsTableElement.appendChild(document.createElement("tr"));
+        supportsRowElement.className = "supports";
+
+        let supportsHeaderElement = supportsRowElement.appendChild(document.createElement("th"));
+        supportsHeaderElement.textContent = WI.unlocalizedString("supports");
+
+        let supportsDataElement = supportsRowElement.appendChild(document.createElement("td"));
+
+        this._supportsInputElement = supportsDataElement.appendChild(document.createElement("input"));
+        this._supportsInputElement.type = "number";
+        this._supportsInputElement.disabled = !this.representedObject.editable;
+        this._supportsInputElement.min = 0;
+        this._supportsInputElement.placeholder = Math.min(WI.AuditTestBase.Version, InspectorBackend.hasDomain("Audit") ? InspectorBackend.getVersion("Audit") : Infinity);
+        if (!isNaN(this.representedObject.supports))
+            this._supportsInputElement.value = this.representedObject.supports;
+
+        if (this.representedObject.editable) {
+            this._supportsInputElement.addEventListener("keydown", (event) => {
+                this._handleEditorKeydown(event, this._setupEditorElement);
+            });
+        }
+
+        this._supportsWarningElement = supportsDataElement.appendChild(document.createElement("span"));
+        this._supportsWarningElement.className = "warning";
+
+        if (this.representedObject.topLevelTest === this.representedObject) {
+            let setupRowElement = controlsTableElement.appendChild(document.createElement("tr"));
+            setupRowElement.className = "setup";
+
+            let setupHeaderElement = setupRowElement.appendChild(document.createElement("th"));
+            setupHeaderElement.textContent = WI.unlocalizedString("setup");
+
+            let setupDataElement = setupRowElement.appendChild(document.createElement("td"));
+
+            this._setupEditorElement = setupDataElement.appendChild(document.createElement("div"));
+        }
+
+        if (this.representedObject.editable) {
+            this._supportsInputElement.addEventListener("input", (event) => {
+                this.representedObject.supports = parseInt(this._supportsInputElement.value);
+
+                this._updateSupportsInputState();
+            });
+        }
+
+        return controlsTableElement;
+    }
+
</ins><span class="cx">     initialLayout()
</span><span class="cx">     {
</span><span class="cx">         super.initialLayout();
</span><span class="lines">@@ -91,8 +230,32 @@
</span><span class="cx">     {
</span><span class="cx">         super.layout();
</span><span class="cx"> 
</span><ins>+        if (this.representedObject instanceof WI.AuditTestBase) {
+            this.element.classList.toggle("unsupported", !this.representedObject.supported);
+            this.element.classList.toggle("disabled", this.representedObject.disabled);
+            this.element.classList.toggle("manager-editing", WI.auditManager.editing);
+
+            if (this.representedObject.editable) {
+                let contentEditable = WI.auditManager.editing ? "plaintext-only" : "inherit";
+                this._nameElement.contentEditable = contentEditable;
+                this._descriptionElement.contentEditable = contentEditable;
+            }
+
+            if (WI.auditManager.editing) {
+                this._cachedName = this.representedObject.name;
+                this._nameElement.dataset.name = this._cachedName;
+
+                this._updateSupportsInputState();
+                this._createSetupEditor();
+            } else {
+                this._nameElement.textContent ||= this._cachedName;
+
+                this._setupEditorElement?.removeChildren();
+            }
+        }
+
</ins><span class="cx">         this.hidePlaceholder();
</span><del>-        this._updateExportButtonNavigationItemState();
</del><ins>+        this._updateExportNavigationItems();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     shown()
</span><span class="lines">@@ -101,18 +264,31 @@
</span><span class="cx"> 
</span><span class="cx">         if (this.representedObject instanceof WI.AuditTestBase) {
</span><span class="cx">             this.representedObject.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestChanged, this);
</span><ins>+            this.representedObject.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
</ins><span class="cx">             this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestChanged, this);
</span><span class="cx">             this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultChanged, this.handleResultChanged, this);
</span><span class="cx">             this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestChanged, this);
</span><span class="cx">             this.representedObject.addEventListener(WI.AuditTestBase.Event.Stopping, this._handleTestChanged, this);
</span><ins>+            this.representedObject.addEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
+
+            WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleEditingChanged, this);
</ins><span class="cx">         }
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     hidden()
</span><span class="cx">     {
</span><del>-        if (this.representedObject instanceof WI.AuditTestBase)
-            this.representedObject.removeEventListener(null, null, this);
</del><ins>+        if (this.representedObject instanceof WI.AuditTestBase) {
+            this.representedObject.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestChanged, this);
+            this.representedObject.removeEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
+            this.representedObject.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestChanged, this);
+            this.representedObject.removeEventListener(WI.AuditTestBase.Event.ResultChanged, this.handleResultChanged, this);
+            this.representedObject.removeEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestChanged, this);
+            this.representedObject.removeEventListener(WI.AuditTestBase.Event.Stopping, this._handleTestChanged, this);
+            this.representedObject.removeEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
</ins><span class="cx"> 
</span><ins>+            WI.auditManager.removeEventListener(WI.AuditManager.Event.EditingChanged, this._handleEditingChanged, this);
+        }
+
</ins><span class="cx">         super.hidden();
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="lines">@@ -120,7 +296,8 @@
</span><span class="cx">     {
</span><span class="cx">         // Overridden by sub-classes.
</span><span class="cx"> 
</span><del>-        this.needsLayout();
</del><ins>+        if (!WI.auditManager.editing)
+            this.needsLayout();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     get placeholderElement()
</span><span class="lines">@@ -152,6 +329,8 @@
</span><span class="cx"> 
</span><span class="cx">             let spinner = new WI.IndeterminateProgressSpinner;
</span><span class="cx">             this.placeholderElement.appendChild(spinner.element);
</span><ins>+
+            this.placeholderElement.appendChild(WI.createReferencePageLink("audit-tab"));
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         this._showPlaceholder();
</span><span class="lines">@@ -169,8 +348,10 @@
</span><span class="cx">                 WI.auditManager.start([this.representedObject]);
</span><span class="cx">             });
</span><span class="cx"> 
</span><del>-            let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to start running the audit"), startNavigationItem);
</del><ins>+            let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to start running the audit."), startNavigationItem);
</ins><span class="cx">             this.placeholderElement.appendChild(importHelpElement);
</span><ins>+
+            this.placeholderElement.appendChild(WI.createReferencePageLink("audit-tab"));
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         this._showPlaceholder();
</span><span class="lines">@@ -203,6 +384,8 @@
</span><span class="cx"> 
</span><span class="cx">             this.placeholderElement = WI.createMessageTextView(message.format(this.representedObject.name), result.didError);
</span><span class="cx">             this.placeholderElement.__placeholderNoResultData = true;
</span><ins>+
+            this.placeholderElement.appendChild(WI.createReferencePageLink("audit-tab"));
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         this._showPlaceholder();
</span><span class="lines">@@ -220,6 +403,8 @@
</span><span class="cx">                 this.resetFilter();
</span><span class="cx">                 this.needsLayout();
</span><span class="cx">             });
</span><ins>+
+            this.placeholderElement.appendChild(WI.createReferencePageLink("audit-tab"));
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         this._showPlaceholder();
</span><span class="lines">@@ -278,11 +463,59 @@
</span><span class="cx">         WI.auditManager.export(this.representedObject.result);
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    _updateExportButtonNavigationItemState()
</del><ins>+    _updateExportNavigationItems()
</ins><span class="cx">     {
</span><del>-        this._exportButtonNavigationItem.enabled = !!this.representedObject.result;
</del><ins>+        if (this._exportTestButtonNavigationItem)
+            this._exportTestButtonNavigationItem.enabled = !WI.auditManager.editing;
+
+        this._exportResultButtonNavigationItem.enabled = !WI.auditManager.editing && this.representedObject.result;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    _updateSupportsInputState()
+    {
+        console.assert(WI.auditManager.editing);
+
+        this._supportsInputElement.autosize(4);
+
+        this._supportsWarningElement.removeChildren();
+        if (this.representedObject.supports > WI.AuditTestBase.Version)
+            this._supportsWarningElement.textContent = WI.UIString("too new to run in this Web Inspector", "too new to run in this Web Inspector @ Audit Tab", "Warning text shown if the version number in the 'supports' input is too new.");
+        else if (InspectorBackend.hasDomain("Audit") && this._supports > InspectorBackend.getVersion("Audit"))
+            this._supportsWarningElement.textContent = WI.UIString("too new to run in the inspected page", "too new to run in the inspected page @ Audit Tab", "Warning text shown if the version number in the 'supports' input is too new.");
+    }
+
+    _createSetupEditor()
+    {
+        if (!this._setupEditorElement)
+            return;
+
+        let setupEditorElement = document.createElement(this._setupEditorElement.nodeName);
+        setupEditorElement.className = "editor";
+
+        // Give the rest of the view a chance to load.
+        setTimeout(() => {
+            let setupCodeMirror = WI.CodeMirrorEditor.create(setupEditorElement, {
+                autoCloseBrackets: true,
+                lineNumbers: true,
+                lineWrapping: true,
+                matchBrackets: true,
+                mode: "text/javascript",
+                readOnly: this.representedObject.editable ? false : "nocursor",
+                styleSelectedText: true,
+                value: this.representedObject.setup,
+            });
+
+            if (this.representedObject.editable) {
+                setupCodeMirror.on("blur", (event) => {
+                    this.representedObject.setup = setupCodeMirror.getValue().trim();
+                });
+            }
+        });
+
+        this._setupEditorElement.parentNode.replaceChild(setupEditorElement, this._setupEditorElement);
+        this._setupEditorElement = setupEditorElement;
+    }
+
</ins><span class="cx">     _showPlaceholder()
</span><span class="cx">     {
</span><span class="cx">         this.element.classList.add("showing-placeholder");
</span><span class="lines">@@ -289,8 +522,36 @@
</span><span class="cx">         this.contentView.element.appendChild(this.placeholderElement);
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    _handleExportButtonNavigationItemClicked(event)
</del><ins>+    _handleEditorKeydown(event, nextEditor)
</ins><span class="cx">     {
</span><ins>+        console.assert(WI.auditManager.editing);
+
+        switch (event.keyCode) {
+        case WI.KeyboardShortcut.Key.Enter.keyCode:
+            if (nextEditor) {
+                nextEditor.focus();
+                break;
+            }
+            // fallthrough
+
+        case WI.KeyboardShortcut.Key.Escape.keyCode:
+            event.target.blur();
+            break;
+
+        default:
+            return;
+        }
+
+        event.preventDefault();
+    }
+
+    _handleExportTestButtonNavigationItemClicked(event)
+    {
+        WI.auditManager.export(this.representedObject);
+    }
+
+    _handleExportResultButtonNavigationItemClicked(event)
+    {
</ins><span class="cx">         this._exportResult();
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="lines">@@ -298,4 +559,25 @@
</span><span class="cx">     {
</span><span class="cx">         this.needsLayout();
</span><span class="cx">     }
</span><ins>+
+    _handleTestDisabledChanged(event)
+    {
+        console.assert(WI.auditManager.editing);
+
+        this.element.classList.toggle("disabled", this.representedObject.disabled);
+    }
+
+    _handleTestSupportedChanged(event)
+    {
+        console.assert(WI.auditManager.editing);
+
+        this.element.classList.toggle("unsupported", !this.representedObject.supported);
+    }
+
+    _handleEditingChanged(event)
+    {
+        this.needsLayout();
+
+        this._updateExportNavigationItems();
+    }
</ins><span class="cx"> };
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestGroupContentViewcss"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.css (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.css    2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.css       2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -27,12 +27,15 @@
</span><span class="cx">     background-color: var(--background-color-content);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.content-view.audit-test-group > header {
</del><ins>+.content-view.audit-test-group > section > .audit-test-group > header {
</ins><span class="cx">     margin-top: -1px;
</span><del>-    -webkit-padding-end: var(--audit-test-horizontal-space);
</del><span class="cx">     border-top: 1px solid var(--border-color);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.content-view.audit-test-group > header {
+    -webkit-padding-end: var(--audit-test-horizontal-space);
+}
+
</ins><span class="cx"> .content-view.audit-test-group.no-matches + .audit-test-group > header {
</span><span class="cx">     border-top: none;
</span><span class="cx"> }
</span><span class="lines">@@ -44,13 +47,18 @@
</span><span class="cx"> 
</span><span class="cx"> .content-view.audit-test-group.contains-test-case > header {
</span><span class="cx">     position: sticky;
</span><del>-    top: -1px;
</del><ins>+    top: 0;
</ins><span class="cx">     z-index: var(--z-index-header);
</span><span class="cx">     background-color: var(--audit-test-header-background-color);
</span><span class="cx">     -webkit-backdrop-filter: blur(20px);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case {
</del><ins>+.content-view.audit-test-group > section > .audit-test-group.contains-test-case > header {
+    top: -1px;
+}
+
+.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case,
+.content-view.audit-test-group + .content-view.audit-test-case {
</ins><span class="cx">     border-top: 1px solid var(--border-color);
</span><span class="cx"> }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTestGroupContentViewjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.js     2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.js        2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -32,14 +32,52 @@
</span><span class="cx">         super(representedObject);
</span><span class="cx"> 
</span><span class="cx">         this.element.classList.add("audit-test-group");
</span><del>-        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));
</del><span class="cx"> 
</span><span class="cx">         this._levelScopeBar = null;
</span><ins>+
+        this._viewForSubobject = new Map;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    // Popover delegate
+
+    willDismissPopover(popover)
+    {
+        console.assert(popover instanceof WI.CreateAuditPopover, popover);
+
+        let audit = popover.audit;
+        if (!audit) {
+            InspectorFrontendHost.beep();
+            return;
+        }
+
+        this.representedObject.addTest(audit);
+    }
+
</ins><span class="cx">     // Protected
</span><span class="cx"> 
</span><ins>+    createControlsTableElement()
+    {
+        let controlsTableElement = super.createControlsTableElement();
+
+        let actionsRowElement = controlsTableElement.appendChild(document.createElement("tr"));
+        actionsRowElement.className = "actions";
+
+        let actionsHeaderElement = controlsTableElement.appendChild(document.createElement("th"));
+        let actionsDataElement = controlsTableElement.appendChild(document.createElement("td"));
+
+        let addTestCaseButtonElement = actionsDataElement.appendChild(document.createElement("button"));
+        addTestCaseButtonElement.disabled = !this.representedObject.editable;
+        addTestCaseButtonElement.textContent = WI.UIString("Add Test Case", "Add Test Case @ Audit Tab - Group", "Text of button to add a new audit test case to the currently shown audit group.");
+        addTestCaseButtonElement.addEventListener("click", (event) => {
+            console.assert(WI.auditManager.editing);
+
+            let popover = new WI.CreateAuditPopover(this);
+            popover.show(addTestCaseButtonElement, [WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X, WI.RectEdge.MIN_X]);
+        });
+
+        return controlsTableElement;
+    }
+
</ins><span class="cx">     initialLayout()
</span><span class="cx">     {
</span><span class="cx">         super.initialLayout();
</span><span class="lines">@@ -47,14 +85,15 @@
</span><span class="cx">         let informationContainer = this.headerView.element.appendChild(document.createElement("div"));
</span><span class="cx">         informationContainer.classList.add("information");
</span><span class="cx"> 
</span><del>-        let nameElement = informationContainer.appendChild(document.createElement("h1"));
-        nameElement.textContent = this.representedObject.name;
</del><ins>+        let nameContainer = informationContainer.appendChild(document.createElement("h1"));
</ins><span class="cx"> 
</span><del>-        if (this.representedObject.description) {
-            let descriptionElement = informationContainer.appendChild(document.createElement("p"));
-            descriptionElement.textContent = this.representedObject.description;
-        }
</del><ins>+        nameContainer.appendChild(this.createNameElement("span"));
</ins><span class="cx"> 
</span><ins>+        informationContainer.appendChild(this.createDescriptionElement("p"));
+
+        if (this.representedObject instanceof WI.AuditTestGroup)
+            informationContainer.appendChild(this.createControlsTableElement());
+
</ins><span class="cx">         this._levelNavigationBar = new WI.NavigationBar(document.createElement("nav"));
</span><span class="cx">         this.headerView.addSubview(this._levelNavigationBar);
</span><span class="cx"> 
</span><span class="lines">@@ -69,6 +108,8 @@
</span><span class="cx">             a.append(b);
</span><span class="cx">             return a;
</span><span class="cx">         });
</span><ins>+
+        this._updateClassList();
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     layout()
</span><span class="lines">@@ -78,6 +119,18 @@
</span><span class="cx"> 
</span><span class="cx">         super.layout();
</span><span class="cx"> 
</span><ins>+        if (WI.auditManager.editing) {
+            if (this._levelScopeBar) {
+                this._levelNavigationBar.removeNavigationItem(this._levelScopeBar);
+                this._levelScopeBar = null;
+            }
+
+            this._percentageContainer.hidden = true;
+
+            this.resetFilter();
+            return;
+        }
+
</ins><span class="cx">         let result = this.representedObject.result;
</span><span class="cx">         if (!result) {
</span><span class="cx">             if (this._levelScopeBar) {
</span><span class="lines">@@ -148,24 +201,34 @@
</span><span class="cx">         if (this.representedObject instanceof WI.AuditTestGroup) {
</span><span class="cx">             this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this);
</span><span class="cx">             this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
</span><ins>+
+            if (this.representedObject.editable) {
+                this.representedObject.addEventListener(WI.AuditTestGroup.Event.TestAdded, this._handleTestGroupTestAdded, this);
+                this.representedObject.addEventListener(WI.AuditTestGroup.Event.TestRemoved, this._handleTestGroupTestRemoved, this);
+            }
</ins><span class="cx">         }
</span><span class="cx"> 
</span><del>-        for (let subobject of this._subobjects()) {
-            if (subobject instanceof WI.AuditTestBase && subobject.disabled)
-                continue;
-
-            let view = WI.ContentView.contentViewForRepresentedObject(subobject);
-            this.contentView.addSubview(view);
-            view.shown();
-        }
</del><ins>+        console.assert(!this._viewForSubobject.size);
+        for (let subobject of this._subobjects())
+            this._addTest(subobject);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     hidden()
</span><span class="cx">     {
</span><del>-        for (let view of this.contentView.subviews)
</del><ins>+        if (this.representedObject instanceof WI.AuditTestGroup) {
+            this.representedObject.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this);
+            this.representedObject.removeEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
+
+            if (this.representedObject.editable) {
+                this.representedObject.removeEventListener(WI.AuditTestGroup.Event.TestAdded, this._handleTestGroupTestAdded, this);
+                this.representedObject.removeEventListener(WI.AuditTestGroup.Event.TestRemoved, this._handleTestGroupTestRemoved, this);
+            }
+        }
+
+        for (let view of this._viewForSubobject.values())
</ins><span class="cx">             view.hidden();
</span><del>-
</del><span class="cx">         this.contentView.removeAllSubviews();
</span><ins>+        this._viewForSubobject.clear();
</ins><span class="cx"> 
</span><span class="cx">         super.hidden();
</span><span class="cx">     }
</span><span class="lines">@@ -196,6 +259,17 @@
</span><span class="cx">             this.placeholderElement.__progress = document.createElement("progress");
</span><span class="cx">             this.placeholderElement.__progress.value = 0;
</span><span class="cx">             this.placeholderElement.appendChild(this.placeholderElement.__progress);
</span><ins>+
+            let stopAuditNavigationItem = new WI.ButtonNavigationItem("stop-audit", WI.UIString("Stop"), "Images/AuditStop.svg", 13, 13);
+            stopAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
+            stopAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, (event) => {
+                WI.auditManager.stop();
+            }, WI.auditManager);
+
+            let stopAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop running."), stopAuditNavigationItem);
+            this.placeholderElement.appendChild(stopAuditHelpElement);
+
+            this.placeholderElement.appendChild(WI.createReferencePageLink("audit-tab"));
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         super.showRunningPlaceholder();
</span><span class="lines">@@ -215,6 +289,14 @@
</span><span class="cx">         return [];
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    _updateClassList()
+    {
+        let subobjects = this._subobjects();
+        let containsTestGroup = subobjects.some((test) => test instanceof WI.AuditTestGroup || test instanceof WI.AuditTestGroupResult);
+        this.element.classList.toggle("contains-test-group", containsTestGroup);
+        this.element.classList.toggle("contains-test-case", !containsTestGroup && subobjects.some((test) => test instanceof WI.AuditTestCase || test instanceof WI.AuditTestCaseResult));
+    }
+
</ins><span class="cx">     _updateLevelScopeBar(levels)
</span><span class="cx">     {
</span><span class="cx">         if (!this._levelScopeBar)
</span><span class="lines">@@ -223,12 +305,23 @@
</span><span class="cx">         for (let item of this._levelScopeBar.items)
</span><span class="cx">             item.selected = levels.includes(item.id);
</span><span class="cx"> 
</span><del>-        for (let view of this.contentView.subviews) {
</del><ins>+        for (let view of this._viewForSubobject.values()) {
</ins><span class="cx">             if (view instanceof WI.AuditTestGroupContentView)
</span><span class="cx">                 view._updateLevelScopeBar(levels);
</span><span class="cx">         }
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    _addTest(test)
+    {
+        console.assert(!this._viewForSubobject.has(test));
+
+        let view = WI.ContentView.contentViewForRepresentedObject(test);
+        this.contentView.addSubview(view);
+        view.shown();
+
+        this._viewForSubobject.set(test, view);
+    }
+
</ins><span class="cx">     _handleTestGroupProgress(event)
</span><span class="cx">     {
</span><span class="cx">         let {index, count} = event.data;
</span><span class="lines">@@ -242,6 +335,32 @@
</span><span class="cx">             this.placeholderElement.__progress.value = 0;
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    _handleTestGroupTestAdded(event)
+    {
+        console.assert(WI.auditManager.editing);
+
+        let {test} = event.data;
+
+        this._addTest(test);
+
+        this._updateClassList();
+    }
+
+    _handleTestGroupTestRemoved(event)
+    {
+        console.assert(WI.auditManager.editing);
+
+        let {test} = event.data;
+
+        let view = this._viewForSubobject.get(test);
+        console.assert(view);
+
+        view.hidden();
+        this.contentView.removeSubview(view);
+
+        this._updateClassList();
+    }
+
</ins><span class="cx">     _handleLevelScopeBarSelectionChanged(event)
</span><span class="cx">     {
</span><span class="cx">         this.needsLayout();
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTreeElementcss"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.css (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.css     2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.css        2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -35,7 +35,7 @@
</span><span class="cx">     height: 100%;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active) > .status:hover > img {
</del><ins>+.tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img {
</ins><span class="cx">     width: 75%;
</span><span class="cx">     height: 75%;
</span><span class="cx">     margin: 0 auto;
</span><span class="lines">@@ -42,13 +42,19 @@
</span><span class="cx">     content: url(../Images/AuditStart.svg) !important;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.tree-outline .item.audit > .status:not(:hover) > img.show-on-hover,
-.tree-outline .item.audit.test-group.expanded:not(.unsupported, .editing-audits) > .status:not(:hover) {
</del><ins>+body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active).selected:hover > .status > img,
+body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit.test-case.selected > .status > .indeterminate-progress-spinner,
+body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit.test-group.selected > .status > progress {
+    filter: var(--filter-invert);
+}
+
+.tree-outline .item.audit:not(:hover) > .status > img.show-on-hover,
+.tree-outline .item.audit.test-group.expanded:not(.unsupported, .editing-audits):not(:hover) > .status {
</ins><span class="cx">     opacity: 0;
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> .tree-outline .item.audit.manager-active > .status > img.show-on-hover,
</span><del>-.tree-outline .item.audit.test-group.expanded:not(.editing-audits) > .status:hover > :not(img),
</del><ins>+.tree-outline .item.audit.test-group.expanded:not(.editing-audits):hover > .status > :not(img),
</ins><span class="cx"> .tree-outline .item.audit.test-group-result.expanded > .status,
</span><span class="cx"> .tree-outline .item.audit.unsupported + .children .item.audit.unsupported  > .status > img {
</span><span class="cx">     display: none;
</span><span class="lines">@@ -102,6 +108,10 @@
</span><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> @media (prefers-color-scheme: dark) {
</span><ins>+    .tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img {
+        filter: var(--filter-invert) brightness(90%);
+    }
+
</ins><span class="cx">     .audit.test-case .icon {
</span><span class="cx">         content: url(../Images/TypeIcons.svg#AuditTestCase-dark);
</span><span class="cx">     }
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsAuditTreeElementjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js      2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js 2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -51,9 +51,16 @@
</span><span class="cx">         super(classNames, representedObject.name, subtitle, representedObject, options);
</span><span class="cx"> 
</span><span class="cx">         if (isTestGroup)
</span><del>-            this._expandedSetting = new WI.Setting(`audit-tree-element-${this.representedObject.name}-expanded`, false);
</del><ins>+            this._expandedSetting = new WI.Setting(WI.AuditTreeElement.expandedSettingKey(this.representedObject.name), false);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    // Static
+
+    static expandedSettingKey(name)
+    {
+        return `audit-tree-element-${name}-expanded`;
+    }
+
</ins><span class="cx">     // Protected
</span><span class="cx"> 
</span><span class="cx">     onattach()
</span><span class="lines">@@ -69,6 +76,14 @@
</span><span class="cx">             else if (this.representedObject instanceof WI.AuditTestGroup)
</span><span class="cx">                 this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
</span><span class="cx"> 
</span><ins>+            if (this.representedObject.editable) {
+                this.representedObject.addEventListener(WI.AuditTestBase.Event.NameChanged, this._handleTestNameChanged, this);
+                this.representedObject.addEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
+
+                if (this.representedObject instanceof WI.AuditTestGroup)
+                    this.representedObject.addEventListener(WI.AuditTestGroup.Event.TestAdded, this._handleTestGroupTestAdded, this);
+            }
+
</ins><span class="cx">             WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleManagerEditingChanged, this);
</span><span class="cx">             WI.auditManager.addEventListener(WI.AuditManager.Event.TestScheduled, this._handleAuditManagerTestScheduled, this);
</span><span class="cx">             WI.auditManager.addEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditManagerTestCompleted, this);
</span><span class="lines">@@ -129,37 +144,72 @@
</span><span class="cx">         if (!(this.representedObject instanceof WI.AuditTestBase))
</span><span class="cx">             return false;
</span><span class="cx"> 
</span><del>-        if (!(this.parent instanceof WI.TreeOutline))
-            return false;
-
</del><span class="cx">         if (!WI.auditManager.editing)
</span><span class="cx">             return false;
</span><span class="cx"> 
</span><del>-        WI.auditManager.removeTest(this.representedObject);
</del><ins>+        this.representedObject.remove();
</ins><span class="cx"> 
</span><span class="cx">         return true;
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    canSelectOnMouseDown(event)
+    {
+        if (this.representedObject instanceof WI.AuditTestBase && this.representedObject.supported && this.status.contains(event.target))
+            return false;
+
+        return super.canSelectOnMouseDown(event);
+    }
+
</ins><span class="cx">     populateContextMenu(contextMenu, event)
</span><span class="cx">     {
</span><del>-        if (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive) {
-            contextMenu.appendItem(WI.UIString("Start"), (event) => {
-                this._start();
-            });
-        }
</del><ins>+        let isTest = this.representedObject instanceof WI.AuditTestBase;
</ins><span class="cx"> 
</span><span class="cx">         contextMenu.appendSeparator();
</span><span class="cx"> 
</span><del>-        if (this.representedObject instanceof WI.AuditTestBase) {
-            contextMenu.appendItem(WI.UIString("Export Test"), (event) => {
-                WI.auditManager.export(this.representedObject);
-            });
-        }
</del><ins>+        if (WI.auditManager.editing) {
+            if (isTest) {
+                if (this.representedObject.supported) {
+                    contextMenu.appendItem(this.representedObject.disabled ? WI.UIString("Enable Audit") : WI.UIString("Disable Audit"), () => {
+                        this.representedObject.disabled = !this.representedObject.disabled;
+                    });
+                }
</ins><span class="cx"> 
</span><del>-        if (this.representedObject.result) {
-            contextMenu.appendItem(WI.UIString("Export Result"), (event) => {
</del><ins>+                contextMenu.appendItem(WI.UIString("Duplicate Audit"), async () => {
+                    let audit = await this.representedObject.clone();
+                    WI.auditManager.addTest(audit);
+                });
+
+                if (this.representedObject.editable) {
+                    contextMenu.appendItem(WI.UIString("Delete Audit"), () => {
+                        this.representedObject.remove();
+                    });
+                }
+            }
+        } else {
+            if (isTest) {
+                contextMenu.appendItem(WI.UIString("Start Audit"), () => {
+                    this._start();
+                }, WI.auditManager.runningState !== WI.AuditManager.RunningState.Inactive);
+
+                contextMenu.appendSeparator();
+
+                contextMenu.appendItem(WI.UIString("Export Audit"), () => {
+                    WI.auditManager.export(this.representedObject);
+                });
+            }
+
+            contextMenu.appendItem(WI.UIString("Export Result"), () => {
</ins><span class="cx">                 WI.auditManager.export(this.representedObject.result);
</span><del>-            });
</del><ins>+            }, !this.representedObject.result);
+
+            if (isTest && this.representedObject.editable) {
+                contextMenu.appendSeparator();
+
+                contextMenu.appendItem(WI.UIString("Edit Audit"), () => {
+                    WI.auditManager.editing = true;
+                    WI.showRepresentedObject(this.representedObject);
+                });
+            }
</ins><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         contextMenu.appendSeparator();
</span><span class="lines">@@ -315,6 +365,26 @@
</span><span class="cx">         this._showRunningProgress();
</span><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    _handleTestNameChanged(event)
+    {
+        this.mainTitle = this.representedObject.name;
+
+        if (this.representedObject instanceof WI.AuditTestGroup)
+            this._expandedSetting = new WI.Setting(WI.AuditTreeElement.expandedSettingKey(this.representedObject.name), !!WI.Setting.migrateValue(WI.AuditTreeElement.expandedSettingKey(event.data.oldName)));
+    }
+
+    _handleTestSupportedChanged(event)
+    {
+        this._updateStatus();
+    }
+
+    _handleTestGroupTestAdded(event)
+    {
+        let {test} = event.data;
+
+        this.appendChild(new WI.AuditTreeElement(test));
+    }
+
</ins><span class="cx">     _handleManagerEditingChanged(event)
</span><span class="cx">     {
</span><span class="cx">         this._updateStatus();
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsCanvasOverviewContentViewcss"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.css (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.css    2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.css       2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -201,14 +201,10 @@
</span><span class="cx"> .navigation-bar > .item.canvas-recording-auto-capture > label > input {
</span><span class="cx">     width: 1.5em;
</span><span class="cx">     min-width: 1.5em;
</span><del>-    margin: 0 var(--recording-auto-capture-input-margin);
</del><ins>+    margin: 0 4px;
</ins><span class="cx">     text-align: center;
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-.navigation-bar > .item.canvas-recording-auto-capture > label > input::-webkit-inner-spin-button {
-    -webkit-appearance: none;
-}
-
</del><span class="cx"> @media (prefers-color-scheme: dark) {
</span><span class="cx">     .content-view.canvas-overview > .content-view.canvas,
</span><span class="cx">     .content-view.canvas-overview > .content-view.canvas > .preview > img {
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsCanvasOverviewContentViewjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.js     2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.js        2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -49,7 +49,6 @@
</span><span class="cx">             this._recordingAutoCaptureFrameCountInputElement = document.createElement("input");
</span><span class="cx">             this._recordingAutoCaptureFrameCountInputElement.type = "number";
</span><span class="cx">             this._recordingAutoCaptureFrameCountInputElement.min = 0;
</span><del>-            this._recordingAutoCaptureFrameCountInputElement.style.setProperty("--recording-auto-capture-input-margin", CanvasOverviewContentView.recordingAutoCaptureInputMargin + "px");
</del><span class="cx">             this._recordingAutoCaptureFrameCountInputElement.addEventListener("input", this._handleRecordingAutoCaptureInput.bind(this));
</span><span class="cx">             this._recordingAutoCaptureFrameCountInputElementValue = WI.settings.canvasRecordingAutoCaptureFrameCount.value;
</span><span class="cx"> 
</span><span class="lines">@@ -69,10 +68,6 @@
</span><span class="cx">         this._savedRecordingsTreeOutline = null;
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    // Static
-
-    static get recordingAutoCaptureInputMargin() { return 4; }
-
</del><span class="cx">     // Public
</span><span class="cx"> 
</span><span class="cx">     get navigationItems()
</span><span class="lines">@@ -226,17 +221,8 @@
</span><span class="cx">             this._recordingAutoCaptureFrameCountInputElementValue = frameCount;
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        WI.ImageUtilities.scratchCanvasContext2D((context) => {
-            if (!this._recordingAutoCaptureFrameCountInputElement.__cachedFont) {
-                let computedStyle = window.getComputedStyle(this._recordingAutoCaptureFrameCountInputElement);
-                this._recordingAutoCaptureFrameCountInputElement.__cachedFont = computedStyle.font;
-            }
</del><ins>+        this._recordingAutoCaptureFrameCountInputElement.autosize();
</ins><span class="cx"> 
</span><del>-            context.font = this._recordingAutoCaptureFrameCountInputElement.__cachedFont;
-            let textMetrics = context.measureText(this._recordingAutoCaptureFrameCountInputElement.value || this._recordingAutoCaptureFrameCountInputElement.placeholder);
-            this._recordingAutoCaptureFrameCountInputElement.style.setProperty("width", (textMetrics.width + (2 * CanvasOverviewContentView.recordingAutoCaptureInputMargin)) + "px");
-        });
-
</del><span class="cx">         return frameCount;
</span><span class="cx">     }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsCreateAuditPopovercss"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.css (0 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.css                           (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.css      2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -0,0 +1,40 @@
</span><ins>+/*
+ * Copyright (C) 2020 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.
+ */
+
+.popover .create-audit-content {
+    padding: 5px;
+    margin: 2px;
+}
+
+.popover .create-audit-content > .editor-wrapper {
+    display: flex;
+    align-items: center;
+    margin-top: 4px;
+}
+
+.popover .create-audit-content > .editor-wrapper > .reference-page-link {
+    margin-bottom: 2px;
+    -webkit-margin-start: 0.5em;
+}
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsCreateAuditPopoverjs"></a>
<div class="addfile"><h4>Added: trunk/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.js (0 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.js                            (rev 0)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.js       2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -0,0 +1,124 @@
</span><ins>+/*
+ * Copyright (C) 2020 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.CreateAuditPopover = class CreateAuditPopover extends WI.Popover
+{
+    constructor(delegate)
+    {
+        super(delegate);
+
+        this._audit = null;
+
+        this._targetElement = null;
+        this._preferredEdges = null;
+
+        this.windowResizeHandler = this._presentOverTargetElement.bind(this);
+    }
+
+    // Public
+
+    get audit() { return this._audit; }
+
+    show(targetElement, preferredEdges)
+    {
+        this._targetElement = targetElement;
+        this._preferredEdges = preferredEdges;
+
+        let contentElement = document.createElement("div");
+        contentElement.classList.add("create-audit-content");
+
+        let label = contentElement.appendChild(document.createElement("div"));
+        label.classList.add("label");
+        label.textContent = WI.UIString("Create audit:");
+
+        let editorWrapper = contentElement.appendChild(document.createElement("div"));
+        editorWrapper.classList.add("editor-wrapper");
+
+        this._typeSelectElement = editorWrapper.appendChild(document.createElement("select"));
+
+        let createOption = (text, value) => {
+            let optionElement = this._typeSelectElement.appendChild(document.createElement("option"));
+            optionElement.textContent = text;
+            optionElement.value = value;
+            return optionElement;
+        };
+
+        createOption(WI.UIString("Test Case", "Test Case @ Audit Tab Navigation Sidebar", "Dropdown option inside the popover used to creating an audit test case."), "test-case");
+        createOption(WI.UIString("Group", "Group @ Audit Tab Navigation Sidebar", "Dropdown option inside the popover used to creating an audit group."), "group");
+
+        this._nameInputElement = editorWrapper.appendChild(document.createElement("input"));
+        this._nameInputElement.placeholder = WI.UIString("Name");
+        this._nameInputElement.addEventListener("keydown", (event) => {
+            if (event.keyCode === WI.KeyboardShortcut.Key.Enter.keyCode || event.keyCode === WI.KeyboardShortcut.Key.Escape.keyCode) {
+                event.stop();
+                this.dismiss();
+            }
+        });
+
+        editorWrapper.appendChild(WI.createReferencePageLink("audit-tab", "creating-audits"));
+
+        this.content = contentElement;
+
+        this._presentOverTargetElement();
+    }
+
+    dismiss()
+    {
+        const placeholderTestFunction = function() {
+            let result = {
+                level: "pass",
+            };
+            return result;
+        };
+        const placeholderTestFunctionString = WI.AuditTestCase.stringifyFunction(placeholderTestFunction, 8);
+
+        let type = this._typeSelectElement.value;
+        let name = this._nameInputElement.value;
+        if (type && name) {
+            switch (type) {
+            case "test-case":
+                this._audit = new WI.AuditTestCase(name, placeholderTestFunctionString);
+                break;
+
+            case "group":
+                this._audit = new WI.AuditTestGroup(name, [new WI.AuditTestCase(WI.unlocalizedString("test-case"), placeholderTestFunctionString)]);
+                break;
+            }
+        }
+
+        super.dismiss();
+    }
+
+    // Private
+
+    _presentOverTargetElement()
+    {
+        if (!this._targetElement)
+            return;
+
+        let targetFrame = WI.Rect.rectFromClientRect(this._targetElement.getBoundingClientRect());
+        this.present(targetFrame.pad(2), this._preferredEdges);
+    }
+};
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsMaincss"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/Main.css (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/Main.css 2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/Main.css    2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -230,9 +230,8 @@
</span><span class="cx"> .navigation-item-help > .navigation-bar {
</span><span class="cx">     display: inline-flex;
</span><span class="cx">     height: 20px;
</span><del>-    padding: 0 4px;
</del><span class="cx">     border-bottom: none;
</span><del>-    vertical-align: -3px;
</del><ins>+    vertical-align: var(--navigation-item-help-navigation-bar-vertical-align, sub);
</ins><span class="cx"> }
</span><span class="cx"> 
</span><span class="cx"> .navigation-item-help > .navigation-bar > .item.button {
</span><span class="lines">@@ -251,6 +250,10 @@
</span><span class="cx">     border: solid 1px var(--border-color);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+.message-text-view > .navigation-item-help + .navigation-item-help {
+    margin-top: 4px;
+}
+
</ins><span class="cx"> .message-text-view.error {
</span><span class="cx">     color: var(--error-text-color);
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsSearchSidebarPaneljs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js    2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js       2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -69,7 +69,7 @@
</span><span class="cx">         let searchNavigationItem = new WI.ButtonNavigationItem("search", WI.UIString("Search Resource Content"), "Images/Search.svg", 15, 15);
</span><span class="cx">         searchNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleDefaultContentViewSearchNavigationItemClicked, this);
</span><span class="cx"> 
</span><del>-        let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to see recent searches"), searchNavigationItem);
</del><ins>+        let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to see recent searches."), searchNavigationItem);
</ins><span class="cx">         contentPlaceholder.appendChild(importHelpElement);
</span><span class="cx"> 
</span><span class="cx">         this.contentBrowser.showContentView(contentView);
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsVariablescss"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css (266316 => 266317)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css    2020-08-29 01:36:21 UTC (rev 266316)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css       2020-08-29 01:39:46 UTC (rev 266317)
</span><span class="lines">@@ -190,6 +190,9 @@
</span><span class="cx"> 
</span><span class="cx">     --focus-ring-outline-offset: -2px;
</span><span class="cx"> 
</span><ins>+    /* Invert colors yet preserve the hue */
+    --filter-invert: invert(100%) hue-rotate(180deg);
+
</ins><span class="cx">     --undocked-title-area-height: 0px;
</span><span class="cx">     --tab-bar-height: var(--navigation-bar-height);
</span><span class="cx">     --navigation-bar-height: 29px;
</span><span class="lines">@@ -362,9 +365,6 @@
</span><span class="cx"> 
</span><span class="cx">         --timeline-scanner-color: hsl(0, 0%, 80%);
</span><span class="cx"> 
</span><del>-        /* Invert colors yet preserve the hue */
-        --filter-invert: invert(100%) hue-rotate(180deg);
-
</del><span class="cx">         /* TODO: Use the same variable for the default theme */
</span><span class="cx">         --overlay-background: hsla(0, 0%, 24%, 0.9);
</span><span class="cx"> 
</span></span></pre>
</div>
</div>

</body>
</html>