Changeset 266317 in webkit


Ignore:
Timestamp:
Aug 28, 2020, 6:39:46 PM (4 years ago)
Author:
Devin Rousso
Message:

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().

Location:
trunk
Files:
2 added
40 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r266300 r266317  
     12020-08-28  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: Audit: should be able to create/edit imported audits
     4        https://bugs.webkit.org/show_bug.cgi?id=215555
     5        <rdar://problem/67255483>
     6
     7        Reviewed by Devin Rousso.
     8
     9        * inspector/model/auditTestGroup.html:
     10        * inspector/model/auditTestGroup-expected.txt:
     11        Add tests that check how `disabled` and `supported` (via `supports`) is propagated in groups.
     12
     13        * inspector/audit/run.html:
     14        * inspector/audit/run-expected.txt:
     15        Add tests for `WebInspectorAudit` before `Audit.setup` is called.
     16
     17        * inspector/audit/manager-start-setup.html:
     18        Renaming function call.
     19
     20        * inspector/audit/basic-async.html:
     21        * inspector/audit/basic-boolean.html:
     22        * inspector/audit/basic-debugger.html:
     23        * inspector/audit/basic-error.html:
     24        * inspector/audit/basic-object.html:
     25        * inspector/audit/basic-promise.html:
     26        * inspector/audit/basic-string.html:
     27        * inspector/audit/basic-timeout.html:
     28        * inspector/audit/run-resources.html:
     29        Drive-by: remove `InspectorTest.debug()`.
     30
    1312020-08-28  Hector Lopez  <hector_i_lopez@apple.com>
    232
  • trunk/LayoutTests/inspector/audit/basic-async.html

    r245257 r266317  
    1010function test()
    1111{
    12     InspectorTest.debug();
    13 
    1412    let suite = InspectorTest.Audit.createSuite("Audit.Basic");
    1513
  • trunk/LayoutTests/inspector/audit/basic-boolean.html

    r245257 r266317  
    1010function test()
    1111{
    12     InspectorTest.debug();
    13 
    1412    let suite = InspectorTest.Audit.createSuite("Audit.Basic");
    1513
  • trunk/LayoutTests/inspector/audit/basic-debugger.html

    r245257 r266317  
    1010function test()
    1111{
    12     InspectorTest.debug();
    13 
    1412    let suite = InspectorTest.Audit.createSuite("Audit.Basic");
    1513
  • trunk/LayoutTests/inspector/audit/basic-error.html

    r245257 r266317  
    1010function test()
    1111{
    12     InspectorTest.debug();
    13 
    1412    let suite = InspectorTest.Audit.createSuite("Audit.Basic");
    1513
  • trunk/LayoutTests/inspector/audit/basic-object.html

    r245257 r266317  
    1010function test()
    1111{
    12     InspectorTest.debug();
    13 
    1412    let suite = InspectorTest.Audit.createSuite("Audit.Basic");
    1513
  • trunk/LayoutTests/inspector/audit/basic-promise.html

    r245257 r266317  
    1010function test()
    1111{
    12     InspectorTest.debug();
    13 
    1412    let suite = InspectorTest.Audit.createSuite("Audit.Basic");
    1513
  • trunk/LayoutTests/inspector/audit/basic-string.html

    r245257 r266317  
    1010function test()
    1111{
    12     InspectorTest.debug();
    13 
    1412    let suite = InspectorTest.Audit.createSuite("Audit.Basic");
    1513
  • trunk/LayoutTests/inspector/audit/basic-timeout.html

    r245257 r266317  
    1010function test()
    1111{
    12     InspectorTest.debug();
    13 
    1412    let suite = InspectorTest.Audit.createSuite("Audit.Basic");
    1513
  • trunk/LayoutTests/inspector/audit/manager-start-setup.html

    r245914 r266317  
    1515
    1616    async function wrapTest(audit, expected, {getResultCallback} = {}) {
    17         WI.auditManager._addTest(audit);
     17        WI.auditManager.addTest(audit);
    1818
    1919        let [result] = await WI.auditManager.start([audit]);
  • trunk/LayoutTests/inspector/audit/run-expected.txt

    r245909 r266317  
    77PASS: Run should send back 42.
    88
    9 -- Running test case: Audit.run.Valid.InjectedObject
     9-- Running test case: Audit.run.Valid.InjectedObject.BeforeSetup
     10Audit run "function() { return WebInspectorAudit; }"...
     11PASS: The injected WebInspectorAudit should be undefined.
     12
     13-- Running test case: Audit.run.Valid.InjectedObject.AfterSetup
    1014Audit setup...
    1115Audit run "function() { return WebInspectorAudit; }"...
  • trunk/LayoutTests/inspector/audit/run-resources.html

    r242941 r266317  
    1010function test()
    1111{
    12     InspectorTest.debug();
    13 
    1412    let suite = InspectorTest.Audit.createSuite("Audit.run.Resources");
    1513
  • trunk/LayoutTests/inspector/audit/run.html

    r251227 r266317  
    3232
    3333    suite.addTestCase({
    34         name: "Audit.run.Valid.InjectedObject",
     34        name: "Audit.run.Valid.InjectedObject.BeforeSetup",
     35        description: "Check that an error is not thrown if trying to reference the injected object without calling setup.",
     36        async test() {
     37            await auditRun(`function() { return WebInspectorAudit; }`, (result) => {
     38                InspectorTest.expectEqual(result.type, "undefined", "The injected WebInspectorAudit should be undefined.");
     39            });
     40        },
     41    });
     42
     43    suite.addTestCase({
     44        name: "Audit.run.Valid.InjectedObject.AfterSetup",
    3545        description: "Check that an object is injected into the function that is executed.",
    3646        async test() {
  • trunk/LayoutTests/inspector/model/auditTestGroup-expected.txt

    r245914 r266317  
    110110}
    111111
     112-- Running test case: AuditTestGroup.Disabled.Constructor.Enabled
     113PASS: Group should not be disabled.
     114PASS: Test1 should not be disabled.
     115PASS: Test2 should not be disabled.
     116
     117-- Running test case: AuditTestGroup.Disabled.Constructor.Disabled
     118PASS: Group should be disabled.
     119PASS: Test1 should be disabled.
     120PASS: Test2 should be disabled.
     121
     122-- Running test case: AuditTestGroup.Disabled.ChangeGroup.Enabled
     123PASS: Group should not be disabled.
     124PASS: Test1 should not be disabled.
     125PASS: Test2 should not be disabled.
     126
     127Marking group as disabled...
     128PASS: Group should be disabled.
     129PASS: Test1 should be disabled.
     130PASS: Test2 should be disabled.
     131
     132-- Running test case: AuditTestGroup.Disabled.ChangeGroup.DisabledGroup
     133PASS: Group should be disabled.
     134PASS: Test1 should be disabled.
     135PASS: Test2 should be disabled.
     136
     137Marking group as enabled...
     138PASS: Group should not be disabled.
     139PASS: Test1 should not be disabled.
     140PASS: Test2 should not be disabled.
     141
     142-- Running test case: AuditTestGroup.Disabled.ChangeGroup.DisabledTests
     143PASS: Group should be disabled.
     144PASS: Test1 should be disabled.
     145PASS: Test2 should be disabled.
     146
     147Marking group as enabled...
     148PASS: Group should not be disabled.
     149PASS: Test1 should not be disabled.
     150PASS: Test2 should not be disabled.
     151
     152-- Running test case: AuditTestGroup.Disabled.ChangeTests.Enabled
     153PASS: Group should not be disabled.
     154PASS: Test1 should not be disabled.
     155PASS: Test2 should not be disabled.
     156
     157Marking Test1 as disabled...
     158PASS: Group should not be disabled.
     159PASS: Test1 should be disabled.
     160PASS: Test2 should not be disabled.
     161
     162Marking Test2 as disabled...
     163PASS: Group should be disabled.
     164PASS: Test1 should be disabled.
     165PASS: Test2 should be disabled.
     166
     167-- Running test case: AuditTestGroup.Disabled.ChangeTests.DisabledGroup
     168PASS: Group should be disabled.
     169PASS: Test1 should be disabled.
     170PASS: Test2 should be disabled.
     171
     172Marking test as enabled 1...
     173PASS: Group should not be disabled.
     174PASS: Test1 should not be disabled.
     175PASS: Test2 should be disabled.
     176
     177Marking test as enabled 2...
     178PASS: Group should not be disabled.
     179PASS: Test1 should not be disabled.
     180PASS: Test2 should not be disabled.
     181
     182-- Running test case: AuditTestGroup.Disabled.ChangeTests.DisabledTests
     183PASS: Group should be disabled.
     184PASS: Test1 should be disabled.
     185PASS: Test2 should be disabled.
     186
     187Marking test as enabled 1...
     188PASS: Group should not be disabled.
     189PASS: Test1 should not be disabled.
     190PASS: Test2 should be disabled.
     191
     192Marking test as enabled 2...
     193PASS: Group should not be disabled.
     194PASS: Test1 should not be disabled.
     195PASS: Test2 should not be disabled.
     196
     197-- Running test case: AuditTestGroup.Supports.Constructor.Supported
     198PASS: Group should be supported.
     199PASS: Test1 should be supported.
     200PASS: Test2 should be supported.
     201
     202-- Running test case: AuditTestGroup.Supports.Constructor.Unsupported
     203WARN: Audit Warning: "Group" is too new to run in this Web Inspector
     204PASS: Group should not be supported.
     205PASS: Test1 should not be supported.
     206PASS: Test2 should not be supported.
     207
     208-- Running test case: AuditTestGroup.Supports.ChangeGroup.Supported
     209PASS: Group should be supported.
     210PASS: Test1 should be supported.
     211PASS: Test2 should be supported.
     212
     213Marking group as unsupported...
     214PASS: Group should not be supported.
     215PASS: Test1 should not be supported.
     216PASS: Test2 should not be supported.
     217
     218-- Running test case: AuditTestGroup.Supports.ChangeGroup.UnsupportedGroup
     219WARN: Audit Warning: "Group" is too new to run in this Web Inspector
     220PASS: Group should not be supported.
     221PASS: Test1 should not be supported.
     222PASS: Test2 should not be supported.
     223
     224Marking group as supported...
     225PASS: Group should be supported.
     226PASS: Test1 should be supported.
     227PASS: Test2 should be supported.
     228
     229-- Running test case: AuditTestGroup.Supports.ChangeGroup.UnsupportedTests
     230WARN: Audit Warning: "Test1" is too new to run in this Web Inspector
     231WARN: Audit Warning: "Test2" is too new to run in this Web Inspector
     232PASS: Group should not be supported.
     233PASS: Test1 should not be supported.
     234PASS: Test2 should not be supported.
     235
     236Marking group as supported...
     237PASS: Group should not be supported.
     238PASS: Test1 should not be supported.
     239PASS: Test2 should not be supported.
     240
     241-- Running test case: AuditTestGroup.Supports.ChangeTests.Supported
     242PASS: Group should be supported.
     243PASS: Test1 should be supported.
     244PASS: Test2 should be supported.
     245
     246Marking Test1 as unsupported...
     247PASS: Group should be supported.
     248PASS: Test1 should not be supported.
     249PASS: Test2 should be supported.
     250
     251Marking Test2 as unsupported...
     252PASS: Group should not be supported.
     253PASS: Test1 should not be supported.
     254PASS: Test2 should not be supported.
     255
     256-- Running test case: AuditTestGroup.Supports.ChangeTests.UnsupportedGroup
     257WARN: Audit Warning: "Group" is too new to run in this Web Inspector
     258PASS: Group should not be supported.
     259PASS: Test1 should not be supported.
     260PASS: Test2 should not be supported.
     261
     262Marking Test1 as supported...
     263PASS: Group should not be supported.
     264PASS: Test1 should not be supported.
     265PASS: Test2 should not be supported.
     266
     267Marking Test2 as supported...
     268PASS: Group should not be supported.
     269PASS: Test1 should not be supported.
     270PASS: Test2 should not be supported.
     271
     272-- Running test case: AuditTestGroup.Supports.ChangeTests.UnsupportedTests
     273WARN: Audit Warning: "Test1" is too new to run in this Web Inspector
     274WARN: Audit Warning: "Test2" is too new to run in this Web Inspector
     275PASS: Group should not be supported.
     276PASS: Test1 should not be supported.
     277PASS: Test2 should not be supported.
     278
     279Marking Test1 as supported...
     280PASS: Group should be supported.
     281PASS: Test1 should be supported.
     282PASS: Test2 should not be supported.
     283
     284Marking Test2 as supported...
     285PASS: Group should be supported.
     286PASS: Test1 should be supported.
     287PASS: Test2 should be supported.
     288
  • trunk/LayoutTests/inspector/model/auditTestGroup.html

    r242808 r266317  
    151151    payloadTests.forEach(addPayloadTest);
    152152
     153    suite.addTestCase({
     154        name: "AuditTestGroup.Disabled.Constructor.Enabled",
     155        description: "Check that `disabled` constructor option properly propagates.",
     156        async test() {
     157            let group = new WI.AuditTestGroup("Group", [
     158                new WI.AuditTestCase("Test1", "function() {}"),
     159                new WI.AuditTestCase("Test2", "function() {}"),
     160            ]);
     161
     162            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
     163            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
     164            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
     165        },
     166    });
     167
     168    suite.addTestCase({
     169        name: "AuditTestGroup.Disabled.Constructor.Disabled",
     170        description: "Check that `disabled` constructor option properly propagates.",
     171        async test() {
     172            let group = new WI.AuditTestGroup("Group", [
     173                new WI.AuditTestCase("Test1", "function() {}"),
     174                new WI.AuditTestCase("Test2", "function() {}"),
     175            ], {disabled: true});
     176
     177            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
     178            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
     179            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
     180        },
     181    });
     182
     183    suite.addTestCase({
     184        name: "AuditTestGroup.Disabled.ChangeGroup.Enabled",
     185        description: "Check that `disabled` is propagated to tests when set on group.",
     186        async test() {
     187            let group = new WI.AuditTestGroup("Group", [
     188                new WI.AuditTestCase("Test1", "function() {}"),
     189                new WI.AuditTestCase("Test2", "function() {}"),
     190            ]);
     191
     192            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
     193            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
     194            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
     195
     196            InspectorTest.newline();
     197            InspectorTest.log("Marking group as disabled...");
     198
     199            group.disabled = true;
     200            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
     201            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
     202            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
     203        },
     204    });
     205
     206    suite.addTestCase({
     207        name: "AuditTestGroup.Disabled.ChangeGroup.DisabledGroup",
     208        description: "Check that `disabled` is propagated to tests when set on group.",
     209        async test() {
     210            let group = new WI.AuditTestGroup("Group", [
     211                new WI.AuditTestCase("Test1", "function() {}"),
     212                new WI.AuditTestCase("Test2", "function() {}"),
     213            ], {disabled: true});
     214
     215            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
     216            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
     217            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
     218
     219            InspectorTest.newline();
     220            InspectorTest.log("Marking group as enabled...");
     221
     222            group.disabled = false;
     223            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
     224            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
     225            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
     226        },
     227    });
     228
     229    suite.addTestCase({
     230        name: "AuditTestGroup.Disabled.ChangeGroup.DisabledTests",
     231        description: "Check that `disabled` is propagated to tests when set on group.",
     232        async test() {
     233            let group = new WI.AuditTestGroup("Group", [
     234                new WI.AuditTestCase("Test1", "function() {}", {disabled: true}),
     235                new WI.AuditTestCase("Test2", "function() {}", {disabled: true}),
     236            ]);
     237
     238            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
     239            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
     240            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
     241
     242            InspectorTest.newline();
     243            InspectorTest.log("Marking group as enabled...");
     244
     245            group.disabled = false;
     246            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
     247            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
     248            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
     249        },
     250    });
     251
     252    suite.addTestCase({
     253        name: "AuditTestGroup.Disabled.ChangeTests.Enabled",
     254        description: "Check that `disabled` is propagated to group when set on tests.",
     255        async test() {
     256            let group = new WI.AuditTestGroup("Group", [
     257                new WI.AuditTestCase("Test1", "function() {}"),
     258                new WI.AuditTestCase("Test2", "function() {}"),
     259            ]);
     260
     261            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
     262            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
     263            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
     264
     265            InspectorTest.newline();
     266            InspectorTest.log("Marking Test1 as disabled...");
     267
     268            group.tests[0].disabled = true;
     269            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
     270            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
     271            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
     272
     273            InspectorTest.newline();
     274            InspectorTest.log("Marking Test2 as disabled...");
     275
     276            group.tests[1].disabled = true;
     277            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
     278            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
     279            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
     280        },
     281    });
     282
     283    suite.addTestCase({
     284        name: "AuditTestGroup.Disabled.ChangeTests.DisabledGroup",
     285        description: "Check that `disabled` is propagated to group when set on tests.",
     286        async test() {
     287            let group = new WI.AuditTestGroup("Group", [
     288                new WI.AuditTestCase("Test1", "function() {}"),
     289                new WI.AuditTestCase("Test2", "function() {}"),
     290            ], {disabled: true});
     291
     292            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
     293            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
     294            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
     295
     296            InspectorTest.newline();
     297            InspectorTest.log("Marking test as enabled 1...");
     298
     299            group.tests[0].disabled = false;
     300            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
     301            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
     302            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
     303
     304            InspectorTest.newline();
     305            InspectorTest.log("Marking test as enabled 2...");
     306
     307            group.tests[1].disabled = false;
     308            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
     309            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
     310            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
     311        },
     312    });
     313
     314    suite.addTestCase({
     315        name: "AuditTestGroup.Disabled.ChangeTests.DisabledTests",
     316        description: "Check that `disabled` is propagated to group when set on tests.",
     317        async test() {
     318            let group = new WI.AuditTestGroup("Group", [
     319                new WI.AuditTestCase("Test1", "function() {}", {disabled: true}),
     320                new WI.AuditTestCase("Test2", "function() {}", {disabled: true}),
     321            ]);
     322
     323            InspectorTest.expectTrue(group.disabled, "Group should be disabled.");
     324            InspectorTest.expectTrue(group.tests[0].disabled, "Test1 should be disabled.");
     325            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
     326
     327            InspectorTest.newline();
     328            InspectorTest.log("Marking test as enabled 1...");
     329
     330            group.tests[0].disabled = false;
     331            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
     332            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
     333            InspectorTest.expectTrue(group.tests[1].disabled, "Test2 should be disabled.");
     334
     335            InspectorTest.newline();
     336            InspectorTest.log("Marking test as enabled 2...");
     337
     338            group.tests[1].disabled = false;
     339            InspectorTest.expectFalse(group.disabled, "Group should not be disabled.");
     340            InspectorTest.expectFalse(group.tests[0].disabled, "Test1 should not be disabled.");
     341            InspectorTest.expectFalse(group.tests[1].disabled, "Test2 should not be disabled.");
     342        },
     343    });
     344
     345    suite.addTestCase({
     346        name: "AuditTestGroup.Supports.Constructor.Supported",
     347        description: "Check that `supported` properly propagates with `supports` constructor option.",
     348        async test() {
     349            let group = new WI.AuditTestGroup("Group", [
     350                new WI.AuditTestCase("Test1", "function() {}"),
     351                new WI.AuditTestCase("Test2", "function() {}"),
     352            ]);
     353
     354            InspectorTest.expectTrue(group.supported, "Group should be supported.");
     355            InspectorTest.expectTrue(group.tests[0].supported, "Test1 should be supported.");
     356            InspectorTest.expectTrue(group.tests[1].supported, "Test2 should be supported.");
     357        },
     358    });
     359
     360    suite.addTestCase({
     361        name: "AuditTestGroup.Supports.Constructor.Unsupported",
     362        description: "Check that `supported` properly propagates with `supports` constructor option.",
     363        async test() {
     364            let group = new WI.AuditTestGroup("Group", [
     365                new WI.AuditTestCase("Test1", "function() {}"),
     366                new WI.AuditTestCase("Test2", "function() {}"),
     367            ], {supports: WI.AuditTestBase.Version + 1});
     368
     369            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
     370            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
     371            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
     372        },
     373    });
     374
     375    suite.addTestCase({
     376        name: "AuditTestGroup.Supports.ChangeGroup.Supported",
     377        description: "Check that `supported` properly propagates to tests when `supports` is set on group.",
     378        async test() {
     379            let group = new WI.AuditTestGroup("Group", [
     380                new WI.AuditTestCase("Test1", "function() {}"),
     381                new WI.AuditTestCase("Test2", "function() {}"),
     382            ]);
     383
     384            InspectorTest.expectTrue(group.supported, "Group should be supported.");
     385            InspectorTest.expectTrue(group.tests[0].supported, "Test1 should be supported.");
     386            InspectorTest.expectTrue(group.tests[1].supported, "Test2 should be supported.");
     387
     388            InspectorTest.newline();
     389            InspectorTest.log("Marking group as unsupported...");
     390
     391            group.supports = WI.AuditTestBase.Version + 1;
     392            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
     393            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
     394            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
     395        },
     396    });
     397
     398    suite.addTestCase({
     399        name: "AuditTestGroup.Supports.ChangeGroup.UnsupportedGroup",
     400        description: "Check that `supported` properly propagates to tests when `supports` is set on group.",
     401        async test() {
     402            let group = new WI.AuditTestGroup("Group", [
     403                new WI.AuditTestCase("Test1", "function() {}"),
     404                new WI.AuditTestCase("Test2", "function() {}"),
     405            ], {supports: WI.AuditTestBase.Version + 1});
     406
     407            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
     408            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
     409            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
     410
     411            InspectorTest.newline();
     412            InspectorTest.log("Marking group as supported...");
     413
     414            group.supports = NaN;
     415            InspectorTest.expectTrue(group.supported, "Group should be supported.");
     416            InspectorTest.expectTrue(group.tests[0].supported, "Test1 should be supported.");
     417            InspectorTest.expectTrue(group.tests[1].supported, "Test2 should be supported.");
     418        },
     419    });
     420
     421    suite.addTestCase({
     422        name: "AuditTestGroup.Supports.ChangeGroup.UnsupportedTests",
     423        description: "Check that `supported` properly propagates to tests when `supports` is set on group.",
     424        async test() {
     425            let group = new WI.AuditTestGroup("Group", [
     426                new WI.AuditTestCase("Test1", "function() {}", {supports: WI.AuditTestBase.Version + 1}),
     427                new WI.AuditTestCase("Test2", "function() {}", {supports: WI.AuditTestBase.Version + 1}),
     428            ]);
     429
     430            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
     431            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
     432            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
     433
     434            InspectorTest.newline();
     435            InspectorTest.log("Marking group as supported...");
     436
     437            group.supports = NaN;
     438            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
     439            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
     440            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
     441        },
     442    });
     443
     444    suite.addTestCase({
     445        name: "AuditTestGroup.Supports.ChangeTests.Supported",
     446        description: "Check that `supported` properly propagates to group when `supports` is set on tests.",
     447        async test() {
     448            let group = new WI.AuditTestGroup("Group", [
     449                new WI.AuditTestCase("Test1", "function() {}"),
     450                new WI.AuditTestCase("Test2", "function() {}"),
     451            ]);
     452
     453            InspectorTest.expectTrue(group.supported, "Group should be supported.");
     454            InspectorTest.expectTrue(group.tests[0].supported, "Test1 should be supported.");
     455            InspectorTest.expectTrue(group.tests[1].supported, "Test2 should be supported.");
     456
     457            InspectorTest.newline();
     458            InspectorTest.log("Marking Test1 as unsupported...");
     459
     460            group.tests[0].supports = WI.AuditTestBase.Version + 1;
     461            InspectorTest.expectTrue(group.supported, "Group should be supported.");
     462            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
     463            InspectorTest.expectTrue(group.tests[1].supported, "Test2 should be supported.");
     464
     465            InspectorTest.newline();
     466            InspectorTest.log("Marking Test2 as unsupported...");
     467
     468            group.tests[1].supports = WI.AuditTestBase.Version + 1;
     469            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
     470            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
     471            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
     472        },
     473    });
     474
     475    suite.addTestCase({
     476        name: "AuditTestGroup.Supports.ChangeTests.UnsupportedGroup",
     477        description: "Check that `supported` properly propagates to group when `supports` is set on tests.",
     478        async test() {
     479            let group = new WI.AuditTestGroup("Group", [
     480                new WI.AuditTestCase("Test1", "function() {}"),
     481                new WI.AuditTestCase("Test2", "function() {}"),
     482            ], {supports: WI.AuditTestBase.Version + 1});
     483
     484            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
     485            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
     486            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
     487
     488            InspectorTest.newline();
     489            InspectorTest.log("Marking Test1 as supported...");
     490
     491            group.tests[0].supports = NaN;
     492            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
     493            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
     494            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
     495
     496            InspectorTest.newline();
     497            InspectorTest.log("Marking Test2 as supported...");
     498
     499            group.tests[1].supports = NaN;
     500            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
     501            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
     502            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
     503        },
     504    });
     505
     506    suite.addTestCase({
     507        name: "AuditTestGroup.Supports.ChangeTests.UnsupportedTests",
     508        description: "Check that `supported` properly propagates to group when `supports` is set on tests.",
     509        async test() {
     510            let group = new WI.AuditTestGroup("Group", [
     511                new WI.AuditTestCase("Test1", "function() {}", {supports: WI.AuditTestBase.Version + 1}),
     512                new WI.AuditTestCase("Test2", "function() {}", {supports: WI.AuditTestBase.Version + 1}),
     513            ]);
     514
     515            InspectorTest.expectFalse(group.supported, "Group should not be supported.");
     516            InspectorTest.expectFalse(group.tests[0].supported, "Test1 should not be supported.");
     517            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
     518
     519            InspectorTest.newline();
     520            InspectorTest.log("Marking Test1 as supported...");
     521
     522            group.tests[0].supports = NaN;
     523            InspectorTest.expectTrue(group.supported, "Group should be supported.");
     524            InspectorTest.expectTrue(group.tests[0].supported, "Test1 should be supported.");
     525            InspectorTest.expectFalse(group.tests[1].supported, "Test2 should not be supported.");
     526
     527            InspectorTest.newline();
     528            InspectorTest.log("Marking Test2 as supported...");
     529
     530            group.tests[1].supports = NaN;
     531            InspectorTest.expectTrue(group.supported, "Group should be supported.");
     532            InspectorTest.expectTrue(group.tests[0].supported, "Test1 should be supported.");
     533            InspectorTest.expectTrue(group.tests[1].supported, "Test2 should be supported.");
     534        },
     535    });
     536
    153537    suite.runTestCasesAndFinish();
    154538}
  • trunk/Source/WebInspectorUI/ChangeLog

    r266227 r266317  
     12020-08-28  Devin Rousso  <drousso@apple.com>
     2
     3        Web Inspector: Audit: should be able to create/edit imported audits
     4        https://bugs.webkit.org/show_bug.cgi?id=215555
     5        <rdar://problem/67255483>
     6
     7        Reviewed by Devin Rousso.
     8
     9        Extend the existing audit edit mode (which previously only let audits be disabled/deleted)
     10        to also allow other JSON properties of audits to be added/edited/removed, meaning that
     11        audits can now be completely created/configured from within the Web Inspector UI. \(o.o)/
     12
     13        * UserInterface/Models/AuditTestBase.js:
     14        (WI.AuditTestBase):
     15        (WI.AuditTestBase.prototype.set name): Added.
     16        (WI.AuditTestBase.prototype.set description): Added.
     17        (WI.AuditTestBase.prototype.get supports): Added.
     18        (WI.AuditTestBase.prototype.set supports): Added.
     19        (WI.AuditTestBase.prototype.get setup): Added.
     20        (WI.AuditTestBase.prototype.set setup): Added.
     21        (WI.AuditTestBase.prototype.set disabled):
     22        (WI.AuditTestBase.prototype.get editable): Added.
     23        (WI.AuditTestBase.prototype.get default): Added.
     24        (WI.AuditTestBase.prototype.markAsDefault): Added.
     25        (WI.AuditTestBase.prototype.get topLevelTest): Added.
     26        (WI.AuditTestBase.prototype.async runSetup): Added.
     27        (WI.AuditTestBase.prototype.async start()):
     28        (WI.AuditTestBase.prototype.stop()):
     29        (WI.AuditTestBase.prototype.async clone): Added.
     30        (WI.AuditTestBase.remove): Added.
     31        (WI.AuditTestBase.saveIdentityToCookie):
     32        (WI.AuditTestBase.toJSON):
     33        (WI.AuditTestBase.prototype.determineIfSupported): Added.
     34        (WI.AuditTestBase.prototype.updateSupported): Added.
     35        (WI.AuditTestBase.prototype.updateResult): Added.
     36        (WI.AuditTestBase.prototype.set supported): Deleted.
     37        (WI.AuditTestBase.async setup): Deleted.
     38        (WI.AuditTestBase.prototype.async setup): Deleted.
     39        * UserInterface/Models/AuditTestGroup.js:
     40        (WI.AuditTestGroup):
     41        (WI.AuditTestGroup.prototype.addTest): Added.
     42        (WI.AuditTestGroup.prototype.removeTest): Added.
     43        (WI.AuditTestGroup.prototype.clearResult):
     44        (WI.AuditTestGroup.prototype.async run):
     45        (WI.AuditTestGroup.prototype.determineIfSupported): Added.
     46        (WI.AuditTestGroup.prototype.updateSupported): Added.
     47        (WI.AuditTestGroup.prototype.updateResult): Added.
     48        (WI.AuditTestGroup.prototype._checkDisabled): Added.
     49        (WI.AuditTestGroup.prototype._handleTestCompleted):
     50        (WI.AuditTestGroup.prototype._handleTestDisabledChanged):
     51        (WI.AuditTestGroup.prototype._handleTestSupportedChanged): Added.
     52        (WI.AuditTestGroup.prototype._handleTestChanged): Added.
     53        (WI.AuditTestGroup.prototype.get supported): Deleted.
     54        (WI.AuditTestGroup.prototype.set supported): Deleted.
     55        (WI.AuditTestGroup.prototype.get disabled): Deleted.
     56        (WI.AuditTestGroup.prototype.set disabled): Deleted.
     57        (WI.AuditTestGroup.prototype._updateResult): Deleted.
     58        (WI.AuditTestGroup.prototype._updateResult): Deleted.
     59        * UserInterface/Models/AuditTestCase.js:
     60        (WI.AuditTestCase):
     61        (WI.AuditTestCase.stringifyFunction): Added.
     62        (WI.AuditTestCase.prototype.set test): Added.
     63        (WI.AuditTestCase.prototype.async run):
     64        Allow additional JSON keys to be changed in the UI:
     65         - `name`
     66         - `description`
     67         - `supports` (ensure that changes recalculate whether the audit is actually supported)
     68         - `setup`
     69         - (groups) `tests` (via separate add and remove functions)
     70         - (test cases) the actual `test` function
     71        These properties are expected to only change in edit mode.
     72
     73        * UserInterface/Models/AuditTestResultBase.js:
     74        (WI.AuditTestResultBase.prototype.get disabled): Added.
     75        (WI.AuditTestResultBase.prototype.get editable): Added.
     76        Audit results are never disabled and not editable.
     77
     78        * UserInterface/Controllers/AuditManager.js:
     79        (WI.AuditManager):
     80        (WI.AuditManager.prototype.set editing):
     81        (WI.AuditManager.async start):
     82        (WI.AuditManager.prototype.async start):
     83        (WI.AuditManager.prototype.stop):
     84        (WI.AuditManager.prototype.async processJSON):
     85        (WI.AuditManager.prototype.loadStoredTests):
     86        (WI.AuditManager.prototype.async addTest): Added.
     87        (WI.AuditManager.prototype.removeTest):
     88        (WI.AuditManager.prototype._addDefaultTests):
     89        (WI.AuditManager.prototype._addDefaultTests.removeWhitespace):
     90        (WI.AuditManager.prototype._addTest): Deleted.
     91        (WI.AuditManager.prototype._topLevelTestForTest): Deleted.
     92        Add helper functions and events for when the audit manager running mode changes to update UI.
     93        Also add another default test that demonstrates the `supports` JSON key.
     94
     95        * UserInterface/Views/AuditNavigationSidebarPanel.js:
     96        (WI.AuditNavigationSidebarPanel.prototype.showDefaultContentView):
     97        (WI.AuditNavigationSidebarPanel.prototype.willDismissPopover): Added.
     98        (WI.AuditNavigationSidebarPanel.prototype.initialLayout):
     99        (WI.AuditNavigationSidebarPanel.prototype.matchTreeElementAgainstCustomFilters):
     100        (WI.AuditNavigationSidebarPanel.prototype._addTest):
     101        (WI.AuditNavigationSidebarPanel.prototype._addResult):
     102        (WI.AuditNavigationSidebarPanel.prototype._updateControlNavigationItems): Added.
     103        (WI.AuditNavigationSidebarPanel.prototype._updateEditNavigationItems): Added.
     104        (WI.AuditNavigationSidebarPanel.prototype._handleAuditManagerEditingChanged):
     105        (WI.AuditNavigationSidebarPanel.prototype._handleAuditManagerRunningStateChanged): Added.
     106        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestAdded):
     107        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestCompleted):
     108        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestRemoved):
     109        (WI.AuditNavigationSidebarPanel.prototype._handleAuditTestScheduled):
     110        (WI.AuditNavigationSidebarPanel.prototype._treeSelectionDidChange):
     111        (WI.AuditNavigationSidebarPanel.prototype._handleStartStopButtonNavigationItemClicked):
     112        (WI.AuditNavigationSidebarPanel.prototype._handleCreateButtonNavigationItemClicked): Added.
     113        (WI.AuditNavigationSidebarPanel.prototype._updateStartStopButtonNavigationItemState): Deleted.
     114        (WI.AuditNavigationSidebarPanel.prototype._updateEditButtonNavigationItemState): Deleted.
     115        * UserInterface/Views/AuditNavigationSidebarPanel.css:
     116        (.sidebar > .panel.navigation.audit .edit-audits:not(.disabled):active): Added.
     117        (.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated): Added.
     118        (.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated:active): Added.
     119        (.sidebar > .panel.navigation.audit .edit-audits.disabled): Added.
     120        (.content-view.audit .message-text-view .navigation-item-help:is(.start-editing-audits, .stop-editing-audits) .navigation-bar): Added.
     121        (.content-view.tab.audit .content-view > .audit-version): Added.
     122        (.content-view.tab.audit .content-view .reference-page-link-container): Added.
     123        (body[dir=ltr] .content-view.tab.audit .content-view .reference-page-link-container): Added.
     124        (body[dir=rtl] .content-view.tab.audit .content-view .reference-page-link-container): Added.
     125        (.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled):active): Deleted.
     126        (.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated): Deleted.
     127        (.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated:active): Deleted.
     128        (.sidebar > .panel.navigation.audit > .content .edit-audits.disabled): Deleted.
     129        (.finish-editing-audits-placeholder.message-text-view .navigation-item-help .navigation-bar): Deleted.
     130        (.audit-version): Deleted.
     131        Move the "Edit" toggle to be next to the filter bar so that it's always visible. Replace the
     132        "[|>] Start" button  with a "[+] Create" button when in edit mode, which adds a new audit at
     133        the top-level.
     134
     135        * UserInterface/Views/AuditTestContentView.js:
     136        (WI.AuditTestContentView):
     137        (WI.AuditTestContentView.get navigationItems):
     138        (WI.AuditTestContentView.get supportsSave):
     139        (WI.AuditTestContentView.prototype.createNameElement): Added.
     140        (WI.AuditTestContentView.prototype.createDescriptionElement): Added.
     141        (WI.AuditTestContentView.prototype.createControlsTableElement): Added.
     142        (WI.AuditTestContentView.prototype.layout):
     143        (WI.AuditTestContentView.prototype.shown):
     144        (WI.AuditTestContentView.prototype.hidden):
     145        (WI.AuditTestContentView.prototype.handleResultChanged):
     146        (WI.AuditTestContentView.prototype.showStoppingPlaceholder):
     147        (WI.AuditTestContentView.prototype.showNoResultPlaceholder):
     148        (WI.AuditTestContentView.prototype.showNoResultDataPlaceholder):
     149        (WI.AuditTestContentView.prototype.showFilteredPlaceholder):
     150        (WI.AuditTestContentView.prototype._updateExportNavigationItems): Added.
     151        (WI.AuditTestContentView.prototype._updateSupportsInputState): Added.
     152        (WI.AuditTestContentView.prototype._createSetupEditor): Added.
     153        (WI.AuditTestContentView.prototype._handleEditorKeydown): Added.
     154        (WI.AuditTestContentView.prototype._handleExportTestButtonNavigationItemClicked): Added.
     155        (WI.AuditTestContentView.prototype._handleExportResultButtonNavigationItemClicked): Added.
     156        (WI.AuditTestContentView.prototype._handleTestDisabledChanged): Added.
     157        (WI.AuditTestContentView.prototype._handleTestSupportedChanged): Added.
     158        (WI.AuditTestContentView.prototype._handleEditingChanged): Added.
     159        (WI.AuditTestContentView.prototype._updateExportButtonNavigationItemState): Deleted.
     160        (WI.AuditTestContentView.prototype._handleExportButtonNavigationItemClicked): Deleted.
     161        * UserInterface/Views/AuditTestContentView.css:
     162        (.content-view.audit-test:is(.unsupported, .disabled):not(.manager-editing)): Added.
     163        (.content-view.audit-test.manager-editing .editor:not(:empty)): Added.
     164        (.content-view.audit-test.manager-editing :is(.content-view.audit-test, header) .editor:not(:empty)): Added.
     165        (.content-view.audit-test .CodeMirror): Added.
     166        (.content-view.audit-test > header :is(.name, .description):not([contenteditable])): Added.
     167        (.content-view.audit-test.manager-editing > header :is(.name, .description)[contenteditable]): Added.
     168        (.content-view.audit-test.manager-editing > header .name[contenteditable]:empty): Added.
     169        (.content-view.audit-test.manager-editing > header .name[contenteditable]:empty:before): Added.
     170        (.content-view.audit-test.manager-editing > header .description[contenteditable]:empty:before): Added.
     171        (.content-view.audit-test:not(.manager-editing) > header .description:empty): Added.
     172        (.content-view.audit-test:not(.manager-editing) > header .description:empty, .content-view.audit-test:not(.manager-editing) > header table.controls): Added.
     173        (.content-view.audit-test > header table.controls, .content-view.audit-test > header table.controls > tr > td): Added.
     174        (.content-view.audit-test > header table.controls > tr > th): Added.
     175        (.content-view.audit-test > header table.controls > tr.supports input[type="number"]): Added.
     176        (.content-view.audit-test > header table.controls > tr.supports .warning): Added.
     177        (.content-view.audit-test > header table.controls > tr.supports .warning:not(:empty)::before ): Added.
     178        (.content-view.audit-test > header table.controls > tr.setup .editor): Added.
     179        (.content-view.audit-test > section > .message-text-view > :is(progress, .indeterminate-progress-spinner)): Added.
     180        (@media (prefers-color-scheme: dark) .content-view.audit-test > header table.controls > tr > th): Added.
     181        Create helper functions for subclasses that simplify creating the editing UI. When in edit
     182        mode, add `contenteditable` to the `name`/`description` and inputs for `supports`/`setup`.
     183
     184        * UserInterface/Views/AuditTestGroupContentView.js:
     185        (WI.AuditTestGroupContentView):
     186        (WI.AuditTestGroupContentView.prototype.willDismissPopover): Added.
     187        (WI.AuditTestGroupContentView.prototype.createControlsTableElement): Added.
     188        (WI.AuditTestGroupContentView.prototype.initialLayout):
     189        (WI.AuditTestGroupContentView.prototype.layout):
     190        (WI.AuditTestGroupContentView.prototype.shown):
     191        (WI.AuditTestGroupContentView.prototype.hidden):
     192        (WI.AuditTestGroupContentView.prototype.showRunningPlaceholder):
     193        (WI.AuditTestGroupContentView.prototype._updateClassList): Added.
     194        (WI.AuditTestGroupContentView.prototype._updateLevelScopeBar):
     195        (WI.AuditTestGroupContentView.prototype._addTest): Added.
     196        (WI.AuditTestGroupContentView.prototype._handleTestGroupTestAdded): Added.
     197        (WI.AuditTestGroupContentView.prototype._handleTestGroupTestRemoved): Added.
     198        * UserInterface/Views/AuditTestGroupContentView.css:
     199        (.content-view.audit-test-group > section > .audit-test-group > header): Added.
     200        (.content-view.audit-test-group.contains-test-case > header):
     201        (.content-view.audit-test-group > section > .audit-test-group.contains-test-case > header): Added.
     202        (.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.
     203        (.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case): Deleted.
     204        When in edit mode, add buttons for removing the audit and adding a new sub-audit (using the
     205        new `WI.CreateAuditPopover` popover).
     206
     207        * UserInterface/Views/AuditTestCaseContentView.js:
     208        (WI.AuditTestCaseContentView):
     209        (WI.AuditTestCaseContentView.prototype.initialLayout):
     210        (WI.AuditTestCaseContentView.prototype.layout):
     211        (WI.AuditTestCaseContentView.prototype.showRunningPlaceholder):
     212        * UserInterface/Views/AuditTestCaseContentView.css:
     213        (.content-view-container > .content-view.audit-test-case): Added.
     214        (.content-view-container > .content-view.audit-test-case > header):
     215        (.content-view-container > .content-view.audit-test-case.manager-editing > header h1 > img): Added.
     216        (.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view, .editor):first-child): Added.
     217        (.content-view-container > .content-view.audit-test-case > section): Added.
     218        (.content-view-container > .content-view.audit-test-case > section, .content-view-container > .content-view.audit-test-case > section :is(.editor, .CodeMirror)): Added.
     219        (.content-view.audit-test-case.manager-editing.disabled:not(.editable) > header h1 > img): Added.
     220        (.content-view.audit-test-case > section > :not(.message-text-view, .editor)): Added.
     221        (.content-view.audit-test-case > section > :not(.message-text-view, .editor):last-child): Added.
     222        (.content-view.audit-test-case > section > :not(.message-text-view, .editor) + :not(.message-text-view, .editor)): Added.
     223        (@media (prefers-color-scheme: dark) .content-view.audit-test-case.manager-editing > header h1 > img): Added.
     224        (.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view):first-child): Deleted.
     225        (.content-view.audit-test-case > section > :not(.message-text-view)): Deleted.
     226        (.content-view.audit-test-case > section > :not(.message-text-view):last-child): Deleted.
     227        (.content-view.audit-test-case > section > :not(.message-text-view) + :not(.message-text-view)): Deleted.
     228        (.content-view.audit-test-case > section .CodeMirror): Deleted.
     229        When in edit mode, replace the icon with a (X) to remove the audit and show a `CodeMirror`
     230        instance to allow editing the content.
     231
     232        * UserInterface/Views/AuditTreeElement.js:
     233        (WI.AuditTreeElement):
     234        (WI.AuditTreeElement.expandedSettingKey): Added.
     235        (WI.AuditTreeElement.prototype.onattach):
     236        (WI.AuditTreeElement.prototype.ondelete):
     237        (WI.AuditTreeElement.prototype.canSelectOnMouseDown): Added.
     238        (WI.AuditTreeElement.prototype.populateContextMenu):
     239        (WI.AuditTreeElement.prototype._handleTestNameChanged): Added.
     240        (WI.AuditTreeElement.prototype._handleTestSupportedChanged): Added.
     241        (WI.AuditTreeElement.prototype._handleTestGroupTestAdded): Added.
     242        * UserInterface/Views/AuditTreeElement.css:
     243        (.tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img): Added.
     244        (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.
     245        (.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.
     246        (.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.
     247        (@media (prefers-color-scheme: dark) .tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img): Added.
     248        (.tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active) > .status:hover > img): Deleted.
     249        (.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.
     250        (.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.
     251        Add context menu items for duplicating/deleting tests when in edit mode. Adjust the label
     252        and disabled state of existing context menu items for clarity.
     253
     254        * UserInterface/Views/CreateAuditPopover.js: Added.
     255        (WI.CreateAuditPopover):
     256        (WI.CreateAuditPopover.prototype.get audit):
     257        (WI.CreateAuditPopover.prototype.show):
     258        (WI.CreateAuditPopover.prototype.dismiss.const.testFunction):
     259        (WI.CreateAuditPopover.prototype.dismiss):
     260        (WI.CreateAuditPopover.prototype._presentOverTargetElement):
     261        * UserInterface/Views/CreateAuditPopover.css: Added.
     262        (.popover .create-audit-content):
     263        (.popover .create-audit-content > .editor-wrapper):
     264        (.popover .create-audit-content > .editor-wrapper > .reference-page-link):
     265        New popover for creating an audit:
     266
     267            [<select> of group or test case] [<input> for name] (?)
     268
     269        * UserInterface/Views/Main.css:
     270        (.navigation-item-help > .navigation-bar):
     271        (.message-text-view > .navigation-item-help + .navigation-item-help): Added.
     272        Add styles for when multiple navigation help items are used in the same message text view.
     273
     274        * UserInterface/Views/Variables.css:
     275        (:root):
     276        (@media (prefers-color-scheme: dark) :root):
     277        Add `--filter-invert` to light mode too.
     278
     279        * UserInterface/Base/Utilities.js:
     280        (HTMLInputElement.prototype.autosize):
     281        * UserInterface/Views/CanvasOverviewContentView.js:
     282        (WI.CanvasOverviewContentView):
     283        (WI.CanvasOverviewContentView.prototype._updateRecordingAutoCaptureInputElementSize):
     284        (WI.CanvasOverviewContentView.get recordingAutoCaptureInputMargin): Deleted.
     285        * UserInterface/Views/CanvasOverviewContentView.css:
     286        (.navigation-bar > .item.canvas-recording-auto-capture > label > input):
     287        (.navigation-bar > .item.canvas-recording-auto-capture > label > input::-webkit-inner-spin-button): Deleted.
     288        Create a helper function for autosizing an `<input>`.
     289
     290        * UserInterface/Views/AuditTabContentView.js:
     291        (WI.AuditTabContentView):
     292        (WI.AuditTabContentView.prototype.initialLayout):
     293        Remove the back/foward arrows as they can get into an inconsistent state when editing.
     294        Drive-by: update the drop zone text for clarity.
     295
     296        * UserInterface/Views/SearchSidebarPanel.js:
     297        (WI.SearchSidebarPanel.prototype.showDefaultContentView):
     298        Drive-by: add period to help text.
     299
     300        * UserInterface/Main.html:
     301        * Localizations/en.lproj/localizedStrings.js:
     302
    13032020-08-27  Carlos Garcia Campos  <cgarcia@igalia.com>
    2304
  • trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js

    r266227 r266317  
    109109localizedStrings["Add New Watch Expression"] = "Add New Watch Expression";
    110110localizedStrings["Add Pattern"] = "Add Pattern";
     111/* Text of button to add a new audit test case to the currently shown audit group. */
     112localizedStrings["Add Test Case @ Audit Tab - Group"] = "Add Test Case";
    111113localizedStrings["Add a Class"] = "Add a Class";
    112114localizedStrings["Add new breakpoint action after this action"] = "Add new breakpoint action after this action";
     
    355357localizedStrings["Count"] = "Count";
    356358localizedStrings["Create %s Rule"] = "Create %s Rule";
     359/* Title of button that creates a new audit. */
     360localizedStrings["Create @ Audit Tab Navigation Sidebar"] = "Create";
    357361localizedStrings["Create Breakpoint"] = "Create Breakpoint";
    358362localizedStrings["Create Local Override"] = "Create Local Override";
    359363localizedStrings["Create Resource"] = "Create Resource";
     364localizedStrings["Create audit:"] = "Create audit:";
    360365localizedStrings["Cross-Origin Restrictions"] = "Cross-Origin Restrictions";
    361366localizedStrings["Current"] = "Current";
     
    391396localizedStrings["Delay"] = "Delay";
    392397localizedStrings["Delete"] = "Delete";
     398localizedStrings["Delete Audit"] = "Delete Audit";
    393399localizedStrings["Delete Blackbox"] = "Delete Blackbox";
    394400localizedStrings["Delete Breakpoint"] = "Delete Breakpoint";
     
    408414localizedStrings["Diagnoses common accessibility problems affecting screen readers and other assistive technology."] = "Diagnoses common accessibility problems affecting screen readers and other assistive technology.";
    409415localizedStrings["Dimensions"] = "Dimensions";
     416localizedStrings["Disable Audit"] = "Disable Audit";
    410417localizedStrings["Disable Breakpoint"] = "Disable Breakpoint";
    411418localizedStrings["Disable Breakpoints"] = "Disable Breakpoints";
     
    441448localizedStrings["Dropped Element"] = "Dropped Element";
    442449localizedStrings["Dropped Node"] = "Dropped Node";
     450localizedStrings["Duplicate Audit"] = "Duplicate Audit";
    443451localizedStrings["Duplicate Selector"] = "Duplicate Selector";
    444452localizedStrings["Duplicate property"] = "Duplicate property";
     
    454462localizedStrings["Edit"] = "Edit";
    455463localizedStrings["Edit %s"] = "Edit %s";
     464localizedStrings["Edit Audit"] = "Edit Audit";
    456465localizedStrings["Edit Breakpoint\u2026"] = "Edit Breakpoint\u2026";
    457466localizedStrings["Edit Local Override\u2026"] = "Edit Local Override\u2026";
     
    461470localizedStrings["Edit configuration"] = "Edit configuration";
    462471localizedStrings["Edit custom gradient"] = "Edit custom gradient";
     472/* Title of icon indiciating that the selected audit is being edited. */
     473localizedStrings["Editing Audit @ Audit Tab - Test Case"] = "Editing audit";
    463474localizedStrings["Editing audits"] = "Editing audits";
    464475localizedStrings["Element"] = "Element";
     
    495506localizedStrings["Elements Tab Name"] = "Elements";
    496507localizedStrings["Emulate User Gesture"] = "Emulate User Gesture";
     508localizedStrings["Enable Audit"] = "Enable Audit";
    497509localizedStrings["Enable Breakpoint"] = "Enable Breakpoint";
    498510localizedStrings["Enable Breakpoints"] = "Enable Breakpoints";
     
    528540localizedStrings["Entire Recording"] = "Entire Recording";
    529541localizedStrings["Error"] = "Error";
     542/* Title of icon indicating that the selected audit threw an error. */
     543localizedStrings["Error @ Audit Tab - Test Case"] = "Error";
    530544localizedStrings["Error: "] = "Error: ";
    531545localizedStrings["Errors"] = "Errors";
     
    551565localizedStrings["Export"] = "Export";
    552566localizedStrings["Export (%s)"] = "Export (%s)";
     567localizedStrings["Export Audit"] = "Export Audit";
    553568localizedStrings["Export HAR"] = "Export HAR";
    554569localizedStrings["Export Result"] = "Export Result";
    555 localizedStrings["Export Test"] = "Export Test";
    556570localizedStrings["Export recording (%s)"] = "Export recording (%s)";
    557571localizedStrings["Export recording (%s)\nShift-click to export a HTML reduction"] = "Export recording (%s)\nShift-click to export a HTML reduction";
    558 localizedStrings["Export result (%s)"] = "Export result (%s)";
     572/* Tooltip for button that exports the most recent result after running an audit. */
     573localizedStrings["Export result (%s) @ Audit Tab"] = "Export result (%s)";
    559574localizedStrings["Expression"] = "Expression";
    560575localizedStrings["Extension Scripts"] = "Extension Scripts";
     
    564579localizedStrings["Extra Style Sheets"] = "Extra Style Sheets";
    565580localizedStrings["Fade unexecuted code"] = "Fade unexecuted code";
     581/* Title of icon indicating that the selected audit failed. */
     582localizedStrings["Fail @ Audit Tab - Test Case"] = "Fail";
    566583localizedStrings["Failed to upgrade"] = "Failed to upgrade";
    567584localizedStrings["Failure status code"] = "Failure status code";
     
    629646localizedStrings["Graphics Tab Name"] = "Graphics";
    630647localizedStrings["Group"] = "Group";
     648/* Dropdown option inside the popover used to creating an audit group. */
     649localizedStrings["Group @ Audit Tab Navigation Sidebar"] = "Group";
    631650localizedStrings["Group By Resource"] = "Group By Resource";
    632651localizedStrings["Group Media Requests"] = "Group Media Requests";
     
    682701localizedStrings["Immediate Pause Requested"] = "Immediate Pause Requested";
    683702localizedStrings["Import"] = "Import";
    684 localizedStrings["Import Audit"] = "Import Audit";
     703localizedStrings["Import Audit or Result"] = "Import Audit or Result";
    685704localizedStrings["Import HAR"] = "Import HAR";
    686705localizedStrings["Import Recording"] = "Import Recording";
     706localizedStrings["Import audit or result"] = "Import audit or result";
    687707localizedStrings["Imported"] = "Imported";
    688708localizedStrings["Imported - %s"] = "Imported - %s";
     
    864884localizedStrings["None"] = "None";
    865885localizedStrings["Not found"] = "Not found";
     886/* Title of icon indicating that the selected audit has not been run yet. */
     887localizedStrings["Not yet run @ Audit Tab - Test Case"] = "Not yet run";
    866888localizedStrings["Object Graph"] = "Object Graph";
    867889localizedStrings["Object Store"] = "Object Store";
     
    910932localizedStrings["Parent"] = "Parent";
    911933localizedStrings["Partial Garbage Collection"] = "Partial Garbage Collection";
     934/* Title of icon indicating that the selected audit passed with no issues. */
     935localizedStrings["Pass @ Audit Tab - Test Case"] = "Pass";
    912936localizedStrings["Passive"] = "Passive";
    913937localizedStrings["Path"] = "Path";
     
    933957localizedStrings["Prefer indent using:"] = "Prefer indent using:";
    934958localizedStrings["Preserve Log"] = "Preserve Log";
    935 localizedStrings["Press %s to import a test or result file"] = "Press %s to import a test or result file";
     959localizedStrings["Press %s to create a new audit."] = "Press %s to create a new audit.";
     960localizedStrings["Press %s to enable audits."] = "Press %s to enable audits.";
     961localizedStrings["Press %s to import an audit or a result."] = "Press %s to import an audit or a result.";
    936962localizedStrings["Press %s to load a recording from file."] = "Press %s to load a recording from file.";
    937 localizedStrings["Press %s to see recent searches"] = "Press %s to see recent searches";
    938 localizedStrings["Press %s to start running the audit"] = "Press %s to start running the audit";
    939 localizedStrings["Press %s to stop editing"] = "Press %s to stop editing";
     963localizedStrings["Press %s to see recent searches."] = "Press %s to see recent searches.";
     964localizedStrings["Press %s to start editing audits."] = "Press %s to start editing audits.";
     965localizedStrings["Press %s to start running the audit."] = "Press %s to start running the audit.";
     966localizedStrings["Press %s to stop editing audits."] = "Press %s to stop editing audits.";
     967localizedStrings["Press %s to stop running."] = "Press %s to stop running.";
    940968localizedStrings["Pressed"] = "Pressed";
    941969localizedStrings["Pretty print"] = "Pretty print";
     
    10931121localizedStrings["Security Issue"] = "Security Issue";
    10941122localizedStrings["Security Origin"] = "Security Origin";
     1123localizedStrings["Select an audit in the navigation sidebar to edit it."] = "Select an audit in the navigation sidebar to edit it.";
     1124localizedStrings["Select an audit in the navigation sidebar to view its results."] = "Select an audit in the navigation sidebar to view its results.";
    10951125localizedStrings["Select baseline snapshot"] = "Select baseline snapshot";
    10961126localizedStrings["Select comparison snapshot"] = "Select comparison snapshot";
     
    11861216localizedStrings["Stalled"] = "Stalled";
    11871217localizedStrings["Start"] = "Start";
     1218localizedStrings["Start Audit"] = "Start Audit";
    11881219localizedStrings["Start Time"] = "Start Time";
    11891220localizedStrings["Start element selection (%s)"] = "Start element selection (%s)";
     
    12381269localizedStrings["Target"] = "Target";
    12391270localizedStrings["Template Content"] = "Template Content";
     1271/* Dropdown option inside the popover used to creating an audit test case. */
     1272localizedStrings["Test Case @ Audit Tab Navigation Sidebar"] = "Test Case";
    12401273localizedStrings["Text"] = "Text";
    12411274localizedStrings["Text Frame"] = "Text Frame";
     
    12811314localizedStrings["This resource was loaded from the disk cache"] = "This resource was loaded from the disk cache";
    12821315localizedStrings["This resource was loaded from the memory cache"] = "This resource was loaded from the memory cache";
     1316localizedStrings["This test should not run because it should be unsupported."] = "This test should not run because it should be unsupported.";
    12831317localizedStrings["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.";
    12841318localizedStrings["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.";
     
    13501384localizedStrings["Unknown error"] = "Unknown error";
    13511385localizedStrings["Unknown node"] = "Unknown node";
     1386/* Title of icon indicating that the selected audit is not able to be run (i.e. unsupported). */
     1387localizedStrings["Unsupported @ Audit Tab - Test Case"] = "Unsupported";
    13521388localizedStrings["Unsupported property name"] = "Unsupported property name";
    13531389localizedStrings["Unsupported property value"] = "Unsupported property value";
     
    13861422localizedStrings["Waiting for frames\u2026"] = "Waiting for frames\u2026";
    13871423localizedStrings["Waiting for transitions created by CSS."] = "Waiting for transitions created by CSS.";
     1424/* Title of icon indicating that the selected audit passed with issues (i.e. warnings). */
     1425localizedStrings["Warn @ Audit Tab - Test Case"] = "Warn";
    13881426localizedStrings["Warning: "] = "Warning: ";
    13891427localizedStrings["Warnings"] = "Warnings";
     
    14721510localizedStrings["\u0022%s\u0022 is not JSON serializable"] = "\u0022%s\u0022 is not JSON serializable";
    14731511localizedStrings["\u0022%s\u0022 is not valid for %s"] = "\u0022%s\u0022 is not valid for %s";
     1512localizedStrings["\u0022%s\u0022 is too new to run in the inspected page"] = "\u0022%s\u0022 is too new to run in the inspected page";
    14741513localizedStrings["\u0022%s\u0022 is too new to run in this Web Inspector"] = "\u0022%s\u0022 is too new to run in this Web Inspector";
    1475 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";
    14761514localizedStrings["\u0022%s\u0022 must be a %s"] = "\u0022%s\u0022 must be a %s";
    14771515localizedStrings["\u0022%s\u0022 must be an %s"] = "\u0022%s\u0022 must be an %s";
     
    15051543localizedStrings["times before stopping"] = "times before stopping";
    15061544localizedStrings["toggle"] = "toggle";
     1545/* Warning text shown if the version number in the 'supports' input is too new. */
     1546localizedStrings["too new to run in the inspected page @ Audit Tab"] = "too new to run in the inspected page";
     1547/* Warning text shown if the version number in the 'supports' input is too new. */
     1548localizedStrings["too new to run in this Web Inspector @ Audit Tab"] = "too new to run in this Web Inspector";
    15071549localizedStrings["unknown %s \u0022%s\u0022"] = "unknown %s \u0022%s\u0022";
    15081550localizedStrings["unsupported %s"] = "unsupported %s";
  • trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js

    r262806 r266317  
    485485});
    486486
     487(function() {
     488    const fontSymbol = Symbol("font");
     489
     490    Object.defineProperty(HTMLInputElement.prototype, "autosize",
     491    {
     492        value(extra = 0)
     493        {
     494            extra += 6; // UserAgent styles add 1px padding and 2px border.
     495            if (this.type === "number")
     496                extra += 13; // Number input inner spin button width.
     497            extra += 2; // Add extra pixels for the cursor.
     498
     499            WI.ImageUtilities.scratchCanvasContext2D((context) => {
     500                this[fontSymbol] ||= window.getComputedStyle(this).font;
     501
     502                context.font = this[fontSymbol];
     503                let textMetrics = context.measureText(this.value || this.placeholder);
     504                this.style.setProperty("width", (textMetrics.width + extra) + "px");
     505            });
     506        },
     507    });
     508})();
     509
    487510Object.defineProperty(Event.prototype, "stop",
    488511{
  • trunk/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js

    r253241 r266317  
    104104            let disabledDefaultTests = [];
    105105            let saveDisabledDefaultTest = (test) => {
    106                 if (test.disabled)
     106                if (test.supported && test.disabled)
    107107                    disabledDefaultTests.push(test.name);
    108108
     
    114114
    115115            for (let test of this._tests) {
    116                 if (test.__default)
     116                if (test.default)
    117117                    saveDisabledDefaultTest(test);
    118118                else
     
    142142
    143143        this._runningState = WI.AuditManager.RunningState.Active;
     144        this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged);
     145
    144146        this._runningTests = tests;
    145147        for (let test of this._runningTests)
     
    157159                await target.AuditAgent.setup();
    158160
    159             let topLevelTest = this._topLevelTestForTest(test);
     161            let topLevelTest = test.topLevelTest;
    160162            console.assert(topLevelTest || window.InspectorTest, "No matching top-level test found", test);
    161163            if (topLevelTest)
    162                 await topLevelTest.setup();
     164                await topLevelTest.runSetup();
    163165
    164166            await test.start();
     
    171173
    172174        this._runningState = WI.AuditManager.RunningState.Inactive;
     175        this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged);
     176
    173177        this._runningTests = [];
    174178
     
    191195
    192196        this._runningState = WI.AuditManager.RunningState.Stopping;
     197        this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged);
    193198
    194199        for (let test of this._runningTests)
     
    219224
    220225        if (object instanceof WI.AuditTestBase) {
    221             this._addTest(object);
     226            this.addTest(object);
    222227            WI.objectStores.audits.putObject(object);
    223228        } else if (object instanceof WI.AuditTestResultBase)
     
    258263                WI.objectStores.audits.associateObject(test, key, payload);
    259264
    260                 this._addTest(test);
     265                this.addTest(test);
    261266            }
    262267        });
    263268    }
    264269
     270    addTest(test)
     271    {
     272        console.assert(test instanceof WI.AuditTestBase, test);
     273        console.assert(!this._tests.includes(test), test);
     274
     275        this._tests.push(test);
     276
     277        this.dispatchEventToListeners(WI.AuditManager.Event.TestAdded, {test});
     278    }
     279
    265280    removeTest(test)
    266281    {
    267         if (test.__default) {
     282        console.assert(this.editing);
     283        console.assert(test instanceof WI.AuditTestBase, test);
     284        console.assert(this._tests.includes(test) || test.default, test);
     285
     286        if (test.default) {
     287            test.clearResult();
     288
    268289            if (test.disabled) {
    269290                InspectorFrontendHost.beep();
     
    280301        }
    281302
     303        console.assert(test.editable, test);
     304
    282305        this._tests.remove(test);
    283306
     
    288311
    289312    // Private
    290 
    291     _addTest(test)
    292     {
    293         this._tests.push(test);
    294 
    295         this.dispatchEventToListeners(WI.AuditManager.Event.TestAdded, {test});
    296     }
    297313
    298314    _addResult(result)
     
    307323            index: this._results.length - 1,
    308324        });
    309     }
    310 
    311     _topLevelTestForTest(test)
    312     {
    313         function walk(group) {
    314             if (group === test)
    315                 return true;
    316             if (group instanceof WI.AuditTestGroup) {
    317                 for (let subtest of group.tests) {
    318                     if (walk(subtest))
    319                         return true;
    320                 }
    321             }
    322             return false;
    323         }
    324 
    325         for (let topLevelTest of this._tests) {
    326             if (walk(topLevelTest))
    327                 return topLevelTest;
    328         }
    329 
    330         return null;
    331325    }
    332326
     
    459453            let mainResource = resources.find((resource) => resource.url === window.location.href);
    460454            return {level: "pass", mainResource, resourceContent: WebInspectorAudit.Resources.getResourceContent(mainResource.id)};
     455        };
     456
     457        const unsupported = function() {
     458            throw Error("this test should not be supported.");
    461459        };
    462460
     
    999997
    1000998        function removeWhitespace(func) {
    1001             return func.toString().replace(/\s+/g, " ");
     999            return WI.AuditTestCase.stringifyFunction(func, 8);
    10021000        }
    10031001
     
    10391037                    ], {description: WI.UIString("These tests demonstrate how to use %s to get information about loaded resources.").format(WI.unlocalizedString("WebInspectorAudit.Resources")), supports: 2}),
    10401038                ], {description: WI.UIString("These tests demonstrate how to use %s to access information not normally available to JavaScript.").format(WI.unlocalizedString("WebInspectorAudit")), supports: 1}),
     1039                new WI.AuditTestCase("unsupported", removeWhitespace(unsupported), {description: WI.UIString("This test should not run because it should be unsupported."), supports: Infinity}),
    10411040            ], {description: WI.UIString("These tests serve as a demonstration of the functionality and structure of audits.")}),
    10421041            new WI.AuditTestGroup(WI.UIString("Accessibility"), [
     
    10671066
    10681067        let checkDisabledDefaultTest = (test) => {
     1068            test.markAsDefault();
     1069
    10691070            if (this._disabledDefaultTestsSetting.value.includes(test.name))
    10701071                test.disabled = true;
     
    10791080            checkDisabledDefaultTest(test);
    10801081
    1081             test.__default = true;
    1082             this._addTest(test);
     1082            this.addTest(test);
    10831083        }
    10841084    }
     
    10941094WI.AuditManager.Event = {
    10951095    EditingChanged: "audit-manager-editing-changed",
     1096    RunningStateChanged: "audit-manager-running-state-changed",
    10961097    TestAdded: "audit-manager-test-added",
    10971098    TestCompleted: "audit-manager-test-completed",
  • trunk/Source/WebInspectorUI/UserInterface/Main.html

    r266074 r266317  
    8383    <link rel="stylesheet" href="Views/CookiePopover.css">
    8484    <link rel="stylesheet" href="Views/CookieStorageContentView.css">
     85    <link rel="stylesheet" href="Views/CreateAuditPopover.css">
    8586    <link rel="stylesheet" href="Views/DOMBreakpointTreeElement.css">
    8687    <link rel="stylesheet" href="Views/DOMEventsBreakdownView.css">
     
    652653    <script src="Views/CookieStorageContentView.js"></script>
    653654    <script src="Views/CookieStorageTreeElement.js"></script>
     655    <script src="Views/CreateAuditPopover.js"></script>
    654656    <script src="Views/DOMBreakpointTreeElement.js"></script>
    655657    <script src="Views/DOMEventsBreakdownView.js"></script>
  • trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js

    r251227 r266317  
    2828    constructor(name, {description, supports, setup, disabled} = {})
    2929    {
    30         console.assert(typeof name === "string");
    31         console.assert(!description || typeof description === "string");
    32         console.assert(supports === undefined || typeof supports === "number");
    33         console.assert(!setup || typeof setup === "string");
    34         console.assert(disabled === undefined || typeof disabled === "boolean");
     30        console.assert(typeof name === "string", name);
     31        console.assert(!description || typeof description === "string", description);
     32        console.assert(supports === undefined || typeof supports === "number", supports);
     33        console.assert(!setup || typeof setup === "string", setup);
     34        console.assert(disabled === undefined || typeof disabled === "boolean", disabled);
    3535
    3636        super();
    3737
    3838        // This class should not be instantiated directly. Create a concrete subclass instead.
    39         console.assert(this.constructor !== WI.AuditTestBase && this instanceof WI.AuditTestBase);
     39        console.assert(this.constructor !== WI.AuditTestBase && this instanceof WI.AuditTestBase, this);
    4040
    4141        this._name = name;
    42         this._description = description || null;
    43         this._supports = supports;
    44         this._setup = setup || null;
    45 
    46         this._supported = true;
    47         if (typeof this._supports === "number") {
    48             if (this._supports > WI.AuditTestBase.Version) {
    49                 WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run in this Web Inspector").format(this.name));
    50                 this._supported = false;
    51             } else if (this._supports > InspectorBackend.getVersion("Audit")) {
    52                 WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run on this inspected page").format(this.name));
    53                 this._supported = false;
    54             }
    55         }
    56 
    57         if (!this.supported)
    58             disabled = true;
     42        this._description = description || "";
     43        this._supports = supports ?? NaN;
     44        this._setup = setup || "";
     45
     46        this.determineIfSupported({warn: true});
    5947
    6048        this._runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive;
    6149        this._result = null;
     50
     51        this._parent = null;
     52
     53        this._default = false;
    6254    }
    6355
    6456    // Public
    6557
    66     get name() { return this._name; }
    67     get description() { return this._description; }
    6858    get runningState() { return this._runningState; }
    6959    get result() { return this._result; }
    70 
    71     get supported()
    72     {
    73         return this._supported;
    74     }
    75 
    76     set supported(supported)
    77     {
    78         this._supported = supported;
    79         if (!this._supported)
    80             this.disabled = true;
     60    get supported() { return this._supported; }
     61
     62    get name()
     63    {
     64        return this._name;
     65    }
     66
     67    set name(name)
     68    {
     69        console.assert(this.editable);
     70        console.assert(WI.auditManager.editing);
     71        console.assert(name && typeof name === "string", name);
     72
     73        if (name === this._name)
     74            return;
     75
     76        let oldName = this._name;
     77        this._name = name;
     78        this.dispatchEventToListeners(WI.AuditTestBase.Event.NameChanged, {oldName});
     79    }
     80
     81    get description()
     82    {
     83        return this._description;
     84    }
     85
     86    set description(description)
     87    {
     88        console.assert(this.editable);
     89        console.assert(WI.auditManager.editing);
     90        console.assert(typeof description === "string", description);
     91
     92        if (description === this._description)
     93            return;
     94
     95        this._description = description;
     96    }
     97
     98    get supports()
     99    {
     100        return this._supports;
     101    }
     102
     103    set supports(supports)
     104    {
     105        console.assert(this.editable);
     106        console.assert(WI.auditManager.editing);
     107        console.assert(typeof supports === "number", supports);
     108
     109        if (supports === this._supports)
     110            return;
     111
     112        this._supports = supports;
     113        this.determineIfSupported();
     114    }
     115
     116    get setup()
     117    {
     118        return this._setup;
     119    }
     120
     121    set setup(setup)
     122    {
     123        console.assert(this.editable);
     124        console.assert(WI.auditManager.editing);
     125        console.assert(typeof setup === "string", setup);
     126
     127        if (setup === this._setup)
     128            return;
     129
     130        this._setup = setup;
     131
     132        this.clearResult();
    81133    }
    82134
     
    88140    set disabled(disabled)
    89141    {
    90         console.assert(this._runningState === WI.AuditManager.RunningState.Disabled || this._runningState === WI.AuditManager.RunningState.Inactive);
    91         if (this._runningState !== WI.AuditManager.RunningState.Disabled && this._runningState !== WI.AuditManager.RunningState.Inactive)
    92             return;
    93 
    94         if (!this.supported)
    95             disabled = true;
    96 
    97         let runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive;
    98         if (runningState === this._runningState)
    99             return;
    100 
    101         this._runningState = runningState;
    102 
    103         this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged);
    104     }
    105 
    106     async setup()
    107     {
     142        this.updateDisabled(disabled);
     143    }
     144
     145    get editable()
     146    {
     147        return !this._default;
     148    }
     149
     150    get default()
     151    {
     152        return this._default;
     153    }
     154
     155    markAsDefault()
     156    {
     157        console.assert(!this._default);
     158        this._default = true;
     159    }
     160
     161    get topLevelTest()
     162    {
     163        let test = this;
     164        while (test._parent)
     165            test = test._parent;
     166        return test;
     167    }
     168
     169    async runSetup()
     170    {
     171        console.assert(this.topLevelTest === this);
     172
    108173        if (!this._setup)
    109174            return;
     
    149214        // Called from WI.AuditManager.
    150215
    151         if (this.disabled)
    152             return;
    153 
    154         console.assert(this.supported);
     216        if (!this._supported || this.disabled)
     217            return;
    155218
    156219        console.assert(WI.auditManager.runningState === WI.AuditManager.RunningState.Active);
     
    172235    {
    173236        // Called from WI.AuditManager.
    174 
    175         if (this.disabled)
    176             return;
    177 
    178         console.assert(this.supported);
     237        // Overridden by sub-classes if needed.
     238
     239        if (!this._supported || this.disabled)
     240            return;
    179241
    180242        console.assert(WI.auditManager.runningState === WI.AuditManager.RunningState.Stopping);
     
    189251    clearResult(options = {})
    190252    {
     253        // Overridden by sub-classes if needed.
     254
    191255        if (!this._result)
    192256            return false;
     
    200264    }
    201265
     266    async clone()
     267    {
     268        console.assert(WI.auditManager.editing);
     269
     270        return this.constructor.fromPayload(this.toJSON());
     271    }
     272
     273    remove()
     274    {
     275        console.assert(WI.auditManager.editing);
     276
     277        if (!this._parent || this._default) {
     278            WI.auditManager.removeTest(this);
     279            return;
     280        }
     281
     282        console.assert(this.editable);
     283        console.assert(this._parent instanceof WI.AuditTestGroup);
     284        this._parent.removeTest(this);
     285    }
     286
    202287    saveIdentityToCookie(cookie)
    203288    {
    204         cookie["audit-" + this.constructor.TypeIdentifier + "-name"] = this._name;
     289        let path = [];
     290        let test = this;
     291        while (test) {
     292            path.push(test.name);
     293            test = test._parent;
     294        }
     295        path.reverse();
     296
     297        cookie["audit-path"] = path.join(",");
    205298    }
    206299
    207300    toJSON(key)
    208301    {
     302        // Overridden by sub-classes if needed.
     303
    209304        let json = {
    210305            type: this.constructor.TypeIdentifier,
     
    213308        if (this._description)
    214309            json.description = this._description;
    215         if (typeof this._supports === "number")
    216             json.supports = this._supports;
     310        if (!isNaN(this._supports))
     311            json.supports = Number.isFinite(this._supports) ? this._supports : WI.AuditTestBase.Version + 1;
    217312        if (this._setup)
    218313            json.setup = this._setup;
     
    228323        throw WI.NotImplementedError.subclassMustOverride();
    229324    }
     325
     326    determineIfSupported(options = {})
     327    {
     328        // Overridden by sub-classes if needed.
     329
     330        let supportedBefore = this._supported;
     331
     332        if (this._supports > WI.AuditTestBase.Version) {
     333            this.updateSupported(false, options);
     334
     335            if (options.warn && supportedBefore !== this._supported && Number.isFinite(this._supports))
     336                WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run in this Web Inspector").format(this.name));
     337        } else if (InspectorBackend.hasDomain("Audit") && this._supports > InspectorBackend.getVersion("Audit")) {
     338            this.updateSupported(false, options);
     339
     340            if (options.warn && supportedBefore !== this._supported && Number.isFinite(this._supports))
     341                WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run in the inspected page").format(this.name));
     342        } else
     343            this.updateSupported(true, options);
     344
     345        return this._supported;
     346    }
     347
     348    updateSupported(supported, options = {})
     349    {
     350        // Overridden by sub-classes if needed.
     351
     352        if (supported === this._supported)
     353            return;
     354
     355        this._supported = supported;
     356
     357        if (!options.silent)
     358            this.dispatchEventToListeners(WI.AuditTestBase.Event.SupportedChanged);
     359
     360        if (!this._supported)
     361            this.clearResult();
     362    }
     363
     364    updateDisabled(disabled, options = {})
     365    {
     366        // Overridden by sub-classes if needed.
     367
     368        console.assert(this._runningState === WI.AuditManager.RunningState.Disabled || this._runningState === WI.AuditManager.RunningState.Inactive);
     369        if (this._runningState !== WI.AuditManager.RunningState.Disabled && this._runningState !== WI.AuditManager.RunningState.Inactive)
     370            return;
     371
     372        let runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive;
     373        if (runningState === this._runningState)
     374            return;
     375
     376        this._runningState = runningState;
     377
     378        if (!options.silent)
     379            this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged);
     380
     381        if (this.disabled)
     382            this.clearResult();
     383    }
     384
     385    updateResult(result)
     386    {
     387        // Overridden by sub-classes if needed.
     388
     389        console.assert(result instanceof WI.AuditTestResultBase, result);
     390
     391        this._result = result;
     392
     393        this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
     394    }
    230395};
    231396
     
    238403    Completed: "audit-test-base-completed",
    239404    DisabledChanged: "audit-test-base-disabled-changed",
     405    NameChanged: "audit-test-base-name-changed",
    240406    Progress: "audit-test-base-progress",
    241407    ResultChanged: "audit-test-base-result-changed",
    242408    Scheduled: "audit-test-base-scheduled",
    243409    Stopping: "audit-test-base-stopping",
     410    SupportedChanged: "audit-test-base-supported-changed",
     411    TestChanged: "audit-test-base-test-changed",
    244412};
  • trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js

    r251227 r266317  
    2828    constructor(name, test, options = {})
    2929    {
    30         console.assert(typeof test === "string");
     30        console.assert(typeof test === "string", test);
    3131
    3232        super(name, options);
     
    7878    }
    7979
     80    static stringifyFunction(func, indentLevel)
     81    {
     82        let string = func.toString();
     83
     84        // Remove spaces to make the function look unindented.
     85        string = string.replaceAll(new RegExp(`^ {${indentLevel}}`, "gm"), "");
     86
     87        // Replace remaining indentations with the user set indent string.
     88        string = string.replaceAll(/^    /gm, WI.indentString());
     89
     90        return string;
     91    }
     92
    8093    // Public
    8194
    82     get test() { return this._test; }
     95    get test()
     96    {
     97        return this._test;
     98    }
     99
     100    set test(test)
     101    {
     102        console.assert(this.editable);
     103        console.assert(WI.auditManager.editing);
     104        console.assert(typeof test === "string", test);
     105
     106        if (test === this._test)
     107            return;
     108
     109        this._test = test;
     110
     111        this.clearResult();
     112
     113        this.dispatchEventToListeners(WI.AuditTestBase.Event.TestChanged);
     114    }
    83115
    84116    toJSON(key)
     
    344376        if (resolvedDOMNodes)
    345377            options.resolvedDOMNodes = resolvedDOMNodes;
    346         this._result = new WI.AuditTestCaseResult(this.name, level, options);
    347 
    348         this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
     378        this.updateResult(new WI.AuditTestCaseResult(this.name, level, options));
    349379    }
    350380};
  • trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js

    r245914 r266317  
    2828    constructor(name, tests, options = {})
    2929    {
    30         console.assert(Array.isArray(tests));
     30        console.assert(Array.isArray(tests), tests);
    3131
    3232        // Set disabled once `_tests` is set so that it propagates.
     
    3636        super(name, options);
    3737
    38         this._tests = tests;
    39         this._preventDisabledPropagation = false;
    40 
    41         if (disabled || !this.supported)
    42             this.disabled = true;
    43 
    44         let hasSupportedTest = false;
    45 
    46         for (let test of this._tests) {
    47             if (!this.supported)
    48                 test.supported = false;
    49             else if (test.supported)
    50                 hasSupportedTest = true;
    51 
    52             test.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this);
    53             test.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
    54             test.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this);
    55 
    56         }
    57 
    58         if (!hasSupportedTest)
    59             this.supported = false;
     38        this._tests = [];
     39        for (let test of tests)
     40            this.addTest(test);
     41
     42        if (disabled)
     43            this.updateDisabled(true);
    6044    }
    6145
     
    122106    get tests() { return this._tests; }
    123107
    124     get supported()
    125     {
    126         return super.supported;
    127     }
    128 
    129     set supported(supported)
    130     {
    131         for (let test of this._tests)
    132             test.supported = supported;
    133 
    134         super.supported = supported;
    135     }
    136 
    137     get disabled()
    138     {
    139         return super.disabled;
    140     }
    141 
    142     set disabled(disabled)
    143     {
    144         if (!this._preventDisabledPropagation) {
    145             for (let test of this._tests)
    146                 test.disabled = disabled;
    147         }
    148 
    149         super.disabled = disabled;
     108    addTest(test)
     109    {
     110        console.assert(test instanceof WI.AuditTestBase, test);
     111        console.assert(!this._tests.includes(test), test);
     112        console.assert(!test._parent, test);
     113
     114        this._tests.push(test);
     115        test._parent = this;
     116
     117        test.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this);
     118        test.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
     119        test.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this);
     120        if (this.editable) {
     121            test.addEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
     122            test.addEventListener(WI.AuditTestBase.Event.TestChanged, this._handleTestChanged, this);
     123        }
     124
     125        this.dispatchEventToListeners(WI.AuditTestGroup.Event.TestAdded, {test});
     126
     127        this.determineIfSupported();
     128
     129        if (this._checkDisabled(test))
     130            test.updateDisabled(true, {silent: true});
     131    }
     132
     133    removeTest(test)
     134    {
     135        console.assert(this.editable);
     136        console.assert(WI.auditManager.editing);
     137        console.assert(test instanceof WI.AuditTestBase, test);
     138        console.assert(test.editable, test);
     139        console.assert(this._tests.includes(test), test);
     140        console.assert(test._parent === this, test);
     141
     142        test.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this);
     143        test.removeEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
     144        test.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this);
     145        test.removeEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
     146        test.removeEventListener(WI.AuditTestBase.Event.TestChanged, this._handleTestChanged, this);
     147
     148        this._tests.remove(test);
     149        test._parent = null;
     150
     151        this.dispatchEventToListeners(WI.AuditTestGroup.Event.TestRemoved, {test});
     152
     153        this.determineIfSupported();
     154
     155        this._checkDisabled();
    150156    }
    151157
     
    162168    clearResult(options = {})
    163169    {
    164         let cleared = !!this._result;
    165         for (let test of this._tests) {
    166             if (test.clearResult(options))
    167                 cleared = true;
     170        let cleared = !!this.result;
     171
     172        if (!options.excludeTests && this._tests) {
     173            for (let test of this._tests) {
     174                if (test.clearResult(options))
     175                    cleared = true;
     176            }
    168177        }
    169178
     
    188197        for (let index = 0; index < count && this._runningState === WI.AuditManager.RunningState.Active; ++index) {
    189198            let test = this._tests[index];
    190             if (test.disabled)
     199            if (test.disabled || !test.supported)
    191200                continue;
    192201
     
    197206        }
    198207
    199         this._updateResult();
    200     }
    201 
    202     // Private
    203 
    204     _updateResult()
     208        this.updateResult();
     209    }
     210
     211    determineIfSupported(options = {})
     212    {
     213        if (this._tests) {
     214            for (let test of this._tests)
     215                test.determineIfSupported({...options, warn: false, silent: true});
     216        }
     217
     218        return super.determineIfSupported(options);
     219    }
     220
     221    updateSupported(supported, options = {})
     222    {
     223        if (this._tests && (!supported || this._tests.every((test) => !test.supported))) {
     224            supported = false;
     225
     226            for (let test of this._tests)
     227                test.updateSupported(supported, {silent: true});
     228        }
     229
     230        super.updateSupported(supported, options);
     231    }
     232
     233    updateDisabled(disabled, options = {})
     234    {
     235        if (!options.excludeTests && this._tests) {
     236            for (let test of this._tests)
     237                test.updateDisabled(disabled, options);
     238        }
     239
     240        super.updateDisabled(disabled, options);
     241    }
     242
     243    updateResult()
    205244    {
    206245        let results = this._tests.map((test) => test.result).filter((result) => !!result);
     
    208247            return;
    209248
    210         this._result = new WI.AuditTestGroupResult(this.name, results, {
     249        super.updateResult(new WI.AuditTestGroupResult(this.name, results, {
    211250            description: this.description,
    212         });
    213 
    214         this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged);
     251        }));
     252    }
     253
     254    // Private
     255
     256    _checkDisabled(test)
     257    {
     258        let testDisabled = !test || !test.supported || test.disabled;
     259        let enabledTestCount = this._tests.filter((existing) => existing.supported && !existing.disabled).length;
     260
     261        if (testDisabled && !enabledTestCount)
     262            this.updateDisabled(true);
     263        else if (!testDisabled && enabledTestCount === 1)
     264            this.updateDisabled(false, {excludeTests: true});
     265        else {
     266            // Don't change `disabled`, as we're currently in an "indeterminate" state.
     267            this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged);
     268        }
     269
     270        return this.disabled;
    215271    }
    216272
     
    220276            return;
    221277
    222         this._updateResult();
     278        this.updateResult();
     279
    223280        this.dispatchEventToListeners(WI.AuditTestBase.Event.Completed);
    224281    }
     
    226283    _handleTestDisabledChanged(event)
    227284    {
    228         let enabledTestCount = this._tests.filter((test) => !test.disabled).length;
    229         if (event.target.disabled && !enabledTestCount)
    230             this.disabled = true;
    231         else if (!event.target.disabled && enabledTestCount === 1) {
    232             this._preventDisabledPropagation = true;
    233             this.disabled = false;
    234             this._preventDisabledPropagation = false;
    235         } else {
    236             // Don't change `disabled`, as we're currently in an "indeterminate" state.
    237             this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged);
    238         }
     285        this._checkDisabled(event.target);
    239286    }
    240287
     
    247294            let count = 0;
    248295            for (let test of tests) {
    249                 if (test.disabled)
     296                if (test.disabled || !test.supported)
    250297                    continue;
    251298
     
    263310        });
    264311    }
     312
     313    _handleTestSupportedChanged(event)
     314    {
     315        this.determineIfSupported();
     316    }
     317
     318    _handleTestChanged(event)
     319    {
     320        console.assert(WI.auditManager.editing);
     321
     322        this.clearResult({excludeTests: true});
     323
     324        this.dispatchEventToListeners(WI.AuditTestBase.Event.TestChanged);
     325    }
    265326};
    266327
    267328WI.AuditTestGroup.TypeIdentifier = "test-group";
     329
     330WI.AuditTestGroup.Event = {
     331    TestAdded: "audit-test-group-test-added",
     332    TestRemoved: "audit-test-group-test-removed",
     333};
  • trunk/Source/WebInspectorUI/UserInterface/Models/AuditTestResultBase.js

    r237613 r266317  
    7070    }
    7171
     72    get disabled()
     73    {
     74        return false;
     75    }
     76
     77    get editable()
     78    {
     79        return false;
     80    }
     81
    7282    saveIdentityToCookie(cookie)
    7383    {
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.css

    r244039 r266317  
    3434}
    3535
    36 .sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled):active {
     36.sidebar > .panel.navigation.audit .edit-audits:not(.disabled):active {
    3737    color: var(--glyph-color-pressed);
    3838}
    3939
    40 .sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated {
     40.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated {
    4141    color: var(--glyph-color-active);
    4242}
    4343
    44 .sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated:active {
     44.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated:active {
    4545    color: var(--glyph-color-active-pressed);
    4646}
    4747
    48 .sidebar > .panel.navigation.audit > .content .edit-audits.disabled {
     48.sidebar > .panel.navigation.audit .edit-audits.disabled {
    4949    color: var(--glyph-color-disabled);
    5050}
     
    5959}
    6060
    61 .finish-editing-audits-placeholder.message-text-view .navigation-item-help .navigation-bar {
    62     padding: 0;
    63     vertical-align: 0.5px;
     61.content-view.audit .message-text-view .navigation-item-help:is(.start-editing-audits, .stop-editing-audits) .navigation-bar {
     62    --navigation-item-help-navigation-bar-vertical-align: 0.5px;
    6463}
    6564
    66 .audit-version {
     65.content-view.tab.audit .content-view > .audit-version {
    6766    position: absolute;
    6867    right: 0;
     
    7574    color: var(--text-color-secondary);
    7675}
     76
     77.content-view.tab.audit .content-view .reference-page-link-container {
     78    position: absolute;
     79    bottom: 4px;
     80}
     81
     82body[dir=ltr] .content-view.tab.audit .content-view .reference-page-link-container {
     83    right: 4px;
     84}
     85
     86body[dir=rtl] .content-view.tab.audit .content-view .reference-page-link-container {
     87    left: 4px;
     88}
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js

    r258246 r266317  
    3131    }
    3232
     33    // Static
     34
     35    static _createNavigationItemTitle()
     36    {
     37        return WI.UIString("Create", "Create @ Audit Tab Navigation Sidebar", "Title of button that creates a new audit.");
     38    }
     39
    3340    // Public
    3441
     
    3946        if (WI.auditManager.editing) {
    4047            let contentPlaceholder = WI.createMessageTextView(WI.UIString("Editing audits"));
    41             contentPlaceholder.classList.add("finish-editing-audits-placeholder");
    4248            contentView.element.appendChild(contentPlaceholder);
    4349
    44             let finishEditingNavigationItem = new WI.ButtonNavigationItem("finish-editing-audits", WI.UIString("Done"));
    45             finishEditingNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, (event) => {
    46                 WI.auditManager.editing = false;
    47             });
    48 
    49             let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop editing"), finishEditingNavigationItem);
    50             contentPlaceholder.appendChild(importHelpElement);
     50            let descriptionElement = contentPlaceholder.appendChild(document.createElement("div"));
     51            descriptionElement.className = "description";
     52            descriptionElement.textContent = WI.UIString("Select an audit in the navigation sidebar to edit it.");
     53
     54            let createAuditNavigationItem = new WI.ButtonNavigationItem("create-audit", WI.AuditNavigationSidebarPanel._createNavigationItemTitle(), "Images/Plus15.svg", 15, 15);
     55            createAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
     56            createAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleCreateButtonNavigationItemClicked, this);
     57
     58            let createAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to create a new audit."), createAuditNavigationItem);
     59            createAuditHelpElement.classList.add("create-audit");
     60            contentPlaceholder.appendChild(createAuditHelpElement);
     61
     62            let stopEditingAuditsNavigationItem = new WI.ButtonNavigationItem("stop-editing-audits", WI.UIString("Done"));
     63            stopEditingAuditsNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleEditButtonNavigationItemClicked, this);
     64
     65            let stopEditingAuditsHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop editing audits."), stopEditingAuditsNavigationItem);
     66            stopEditingAuditsHelpElement.classList.add("stop-editing-audits");
     67            contentPlaceholder.appendChild(stopEditingAuditsHelpElement);
    5168        } else {
     69            let hasEnabledAudit = WI.auditManager.tests.length && WI.auditManager.tests.some((test) => !test.disabled && test.supported);
     70
    5271            let contentPlaceholder = WI.createMessageTextView(WI.UIString("No audit selected"));
    5372            contentView.element.appendChild(contentPlaceholder);
    5473
    55             let importNavigationItem = new WI.ButtonNavigationItem("import-audit", WI.UIString("Import"), "Images/Import.svg", 15, 15);
    56             importNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
    57             importNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
    58 
    59             let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to import a test or result file"), importNavigationItem);
    60             contentPlaceholder.appendChild(importHelpElement);
     74            if (hasEnabledAudit) {
     75                let descriptionElement = contentPlaceholder.appendChild(document.createElement("div"));
     76                descriptionElement.className = "description";
     77                descriptionElement.textContent = WI.UIString("Select an audit in the navigation sidebar to view its results.");
     78            }
     79
     80            let importAuditNavigationItem = new WI.ButtonNavigationItem("import-audit", WI.UIString("Import"), "Images/Import.svg", 15, 15);
     81            importAuditNavigationItem.title = WI.UIString("Import audit or result");
     82            importAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
     83            importAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
     84
     85            let importAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to import an audit or a result."), importAuditNavigationItem);
     86            importAuditHelpElement.classList.add("import-audit");
     87            contentPlaceholder.appendChild(importAuditHelpElement);
     88
     89            let startEditingAuditsNavigationItem = new WI.ButtonNavigationItem("start-editing-audits", WI.UIString("Edit"));
     90            startEditingAuditsNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleEditButtonNavigationItemClicked, this);
     91
     92            let startEditingAuditsHelpElement = WI.createNavigationItemHelp(hasEnabledAudit ? WI.UIString("Press %s to start editing audits.") : WI.UIString("Press %s to enable audits."), startEditingAuditsNavigationItem);
     93            startEditingAuditsHelpElement.classList.add("start-editing-audits");
     94            contentPlaceholder.appendChild(startEditingAuditsHelpElement);
    6195        }
    6296
     
    69103        versionContainer.textContent = WI.UIString("Audit version: %s").format(version);
    70104
     105        versionContainer.appendChild(WI.createReferencePageLink("audit-tab"));
     106
    71107        this.contentBrowser.showContentView(contentView);
    72108    }
    73109
     110    // Popover delegate
     111
     112    willDismissPopover(popover)
     113    {
     114        console.assert(popover instanceof WI.CreateAuditPopover, popover);
     115
     116        let audit = popover.audit;
     117        if (!audit) {
     118            InspectorFrontendHost.beep();
     119            return;
     120        }
     121
     122        WI.auditManager.addTest(audit);
     123
     124        WI.showRepresentedObject(audit);
     125    }
     126
    74127    // Protected
    75128
     
    82135        let controlsNavigationBar = new WI.NavigationBar;
    83136
    84         this._startStopButtonNavigationItem = new WI.ToggleButtonNavigationItem("audit-start-stop", WI.UIString("Start"), WI.UIString("Stop"), "Images/AuditStart.svg", "Images/AuditStop.svg", 13, 13);
     137        this._startStopButtonNavigationItem = new WI.ToggleButtonNavigationItem("start-stop-audit", WI.UIString("Start"), WI.UIString("Stop"), "Images/AuditStart.svg", "Images/AuditStop.svg", 13, 13);
    85138        this._startStopButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
    86         this._updateStartStopButtonNavigationItemState();
    87139        this._startStopButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleStartStopButtonNavigationItemClicked, this);
    88140        controlsNavigationBar.addNavigationItem(this._startStopButtonNavigationItem);
    89141
     142        this._createButtonNavigationItem = new WI.ButtonNavigationItem("create-audit", WI.AuditNavigationSidebarPanel._createNavigationItemTitle(), "Images/Plus15.svg", 15, 15);
     143        this._createButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
     144        this._createButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleCreateButtonNavigationItemClicked, this);
     145        controlsNavigationBar.addNavigationItem(this._createButtonNavigationItem);
     146
    90147        controlsNavigationBar.addNavigationItem(new WI.DividerNavigationItem);
    91148
    92         let importButtonNavigationItem = new WI.ButtonNavigationItem("audit-import", WI.UIString("Import"), "Images/Import.svg", 15, 15);
     149        let importButtonNavigationItem = new WI.ButtonNavigationItem("import-audit", WI.UIString("Import"), "Images/Import.svg", 15, 15);
     150        importButtonNavigationItem.title = WI.UIString("Import audit or result");
    93151        importButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
    94         importButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
    95152        importButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this);
    96153        controlsNavigationBar.addNavigationItem(importButtonNavigationItem);
     
    98155        this.addSubview(controlsNavigationBar);
    99156
    100         let editNavigationbar = new WI.NavigationBar;
    101 
    102157        this._editButtonNavigationItem = new WI.ActivateButtonNavigationItem("edit-audits", WI.UIString("Edit"), WI.UIString("Done"));
    103158        this._editButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleEditButtonNavigationItemClicked, this);
    104         editNavigationbar.addNavigationItem(this._editButtonNavigationItem);
    105 
    106         this.contentView.addSubview(editNavigationbar);
     159        this.filterBar.addFilterNavigationItem(this._editButtonNavigationItem);
    107160
    108161        for (let test of WI.auditManager.tests)
     
    113166        });
    114167
     168        this._updateControlNavigationItems();
     169        this._updateEditNavigationItems();
     170        this._updateNoAuditsPlaceholder();
     171
     172        WI.AuditTestGroup.addEventListener(WI.AuditTestGroup.Event.TestRemoved, this._handleAuditTestRemoved, this);
     173
    115174        WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleAuditManagerEditingChanged, this);
     175        WI.auditManager.addEventListener(WI.AuditManager.Event.RunningStateChanged, this._handleAuditManagerRunningStateChanged, this);
    116176        WI.auditManager.addEventListener(WI.AuditManager.Event.TestAdded, this._handleAuditTestAdded, this);
    117177        WI.auditManager.addEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditTestCompleted, this);
     
    148208                return false;
    149209        } else {
    150             if (treeElement.representedObject instanceof WI.AuditTestBase && treeElement.representedObject.disabled)
     210            if (treeElement.representedObject instanceof WI.AuditTestBase && (treeElement.representedObject.disabled || !treeElement.representedObject.supported))
    151211                return false;
    152212        }
     
    166226        } else
    167227            this.contentTreeOutline.appendChild(treeElement);
    168 
    169         this._updateStartStopButtonNavigationItemState();
    170         this._updateEditButtonNavigationItemState();
    171         this._updateNoAuditsPlaceholder();
    172228    }
    173229
     
    194250        for (let resultItem of result)
    195251            resultFolderTreeElement.appendChild(new WI.AuditTreeElement(resultItem));
    196 
    197         this._updateStartStopButtonNavigationItemState();
    198         this._updateEditButtonNavigationItemState();
    199     }
    200 
    201     _updateStartStopButtonNavigationItemState()
     252    }
     253
     254    _updateControlNavigationItems()
    202255    {
    203256        this._startStopButtonNavigationItem.toggled = WI.auditManager.runningState === WI.AuditManager.RunningState.Active || WI.auditManager.runningState === WI.AuditManager.RunningState.Stopping;
    204         this._startStopButtonNavigationItem.enabled = WI.auditManager.tests.some((test) => !test.disabled) && (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive || WI.auditManager.runningState === WI.AuditManager.RunningState.Active);
    205     }
    206 
    207      _updateEditButtonNavigationItemState()
     257        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);
     258        this._startStopButtonNavigationItem.hidden = WI.auditManager.editing;
     259
     260        this._createButtonNavigationItem.hidden = !WI.auditManager.editing;
     261    }
     262
     263     _updateEditNavigationItems()
    208264    {
    209265        this._editButtonNavigationItem.label = WI.auditManager.editing ? this._editButtonNavigationItem.activatedToolTip : this._editButtonNavigationItem.defaultToolTip;
    210266        this._editButtonNavigationItem.activated = WI.auditManager.editing;
    211         this._editButtonNavigationItem.enabled = WI.auditManager.tests.length && (WI.auditManager.editing || WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive);
     267        this._editButtonNavigationItem.enabled = WI.auditManager.editing || WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive;
    212268    }
    213269
    214270    _updateNoAuditsPlaceholder()
    215271    {
    216         if (WI.auditManager.editing || WI.auditManager.tests.some((test) => !test.disabled)) {
     272        if (WI.auditManager.editing || WI.auditManager.tests.some((test) => !test.disabled && test.supported)) {
    217273            if (!this.hasActiveFilters)
    218274                this.hideEmptyContentPlaceholder();
     
    232288    _handleAuditManagerEditingChanged(event)
    233289    {
    234         if (WI.auditManager.editing) {
    235             console.assert(!this._selectedTreeElementBeforeEditing);
    236             this._selectedTreeElementBeforeEditing = this.contentTreeOutline.selectedTreeElement;
    237             if (this._selectedTreeElementBeforeEditing)
    238                 this._selectedTreeElementBeforeEditing.deselect();
    239         } else if (this._selectedTreeElementBeforeEditing) {
    240             if (this.contentTreeOutline.selectedTreeElement === this._selectedTreeElementBeforeEditing) {
    241                 const suppressNotification = true;
    242                 this._selectedTreeElementBeforeEditing.deselect(suppressNotification);
     290        let previousSelectedTreeElement = this.contentTreeOutline.selectedTreeElement;
     291        if (previousSelectedTreeElement) {
     292            if (WI.auditManager.editing) {
     293                if (!(previousSelectedTreeElement.representedObject instanceof WI.AuditTestBase))
     294                    previousSelectedTreeElement.deselect();
     295            } else {
     296                if (previousSelectedTreeElement.representedObject.disabled || !previousSelectedTreeElement.representedObject.supported)
     297                    previousSelectedTreeElement.deselect();
    243298            }
    244             if (!(this._selectedTreeElementBeforeEditing.representedObject instanceof WI.AuditTestBase) || !this._selectedTreeElementBeforeEditing.representedObject.disabled) {
    245                 const omitFocus = false;
    246                 const selectedByUser = true;
    247                 this._selectedTreeElementBeforeEditing.select(omitFocus, selectedByUser);
    248             }
    249             this._selectedTreeElementBeforeEditing = null;
    250         }
     299        }
     300
     301        this.updateFilter();
    251302
    252303        if (!this.contentTreeOutline.selectedTreeElement)
    253304            this.showDefaultContentView();
    254305
    255         this._updateStartStopButtonNavigationItemState();
    256         this._updateEditButtonNavigationItemState();
    257 
    258         this.updateFilter();
     306        this._updateControlNavigationItems();
     307        this._updateEditNavigationItems();
     308        this._updateNoAuditsPlaceholder();
     309    }
     310
     311    _handleAuditManagerRunningStateChanged(event)
     312    {
     313        this._updateControlNavigationItems();
     314        this._updateEditNavigationItems();
    259315    }
    260316
    261317    _handleAuditTestAdded(event)
    262318    {
    263         this._addTest(event.data.test);
     319        let {test} = event.data;
     320
     321        this._addTest(test);
     322
     323        this._updateControlNavigationItems();
     324        this._updateNoAuditsPlaceholder();
    264325    }
    265326
     
    268329        let {result, index} = event.data;
    269330        this._addResult(result, index);
     331
     332        this._updateControlNavigationItems();
     333        this._updateEditNavigationItems();
    270334    }
    271335
    272336    _handleAuditTestRemoved(event)
    273337    {
     338        console.assert(WI.auditManager.editing);
     339
    274340        let {test} = event.data;
     341
    275342        let treeElement = this.treeElementForRepresentedObject(test);
    276         this.contentTreeOutline.removeChild(treeElement);
    277 
    278         this._updateStartStopButtonNavigationItemState();
    279         this._updateEditButtonNavigationItemState();
    280         this._updateNoAuditsPlaceholder();
     343        treeElement.parent.removeChild(treeElement);
     344
     345        this._updateControlNavigationItems();
    281346    }
    282347
    283348    _handleAuditTestScheduled(event)
    284349    {
    285         this._updateStartStopButtonNavigationItemState();
    286         this._updateEditButtonNavigationItemState();
     350        this._updateControlNavigationItems();
     351        this._updateEditNavigationItems();
    287352    }
    288353
     
    297362            return;
    298363        }
    299 
    300         if (WI.auditManager.editing)
    301             return;
    302364
    303365        let representedObject = treeElement.representedObject;
     
    317379        else if (WI.auditManager.runningState === WI.AuditManager.RunningState.Active)
    318380            WI.auditManager.stop();
    319 
    320         this._updateStartStopButtonNavigationItemState();
     381    }
     382
     383    _handleCreateButtonNavigationItemClicked(event)
     384    {
     385        console.assert(WI.auditManager.editing);
     386
     387        let popover = new WI.CreateAuditPopover(this);
     388        popover.show(event.target.element, [WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X, WI.RectEdge.MIN_X]);
    321389    }
    322390
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTabContentView.js

    r265707 r266317  
    3030        super(AuditTabContentView.tabInfo(), {
    3131            navigationSidebarPanelConstructor: WI.AuditNavigationSidebarPanel,
     32            disableBackForward: true,
    3233        });
    3334
     
    112113
    113114        let dropZoneView = new WI.DropZoneView(this);
    114         dropZoneView.text = WI.UIString("Import Audit");
     115        dropZoneView.text = WI.UIString("Import Audit or Result");
    115116        dropZoneView.targetElement = this.element;
    116117        this.addSubview(dropZoneView);
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css

    r245914 r266317  
    2424 */
    2525
     26.content-view-container > .content-view.audit-test-case {
     27    display: flex;
     28    flex-direction: column;
     29    height: 100%;
     30    overflow: hidden;
     31}
     32
    2633.content-view-container > .content-view.audit-test-case > header {
     34    flex-shrink: 0;
    2735    position: sticky;
    28     top: -1px;
     36    top: 0;
    2937    z-index: var(--z-index-header);
    30     margin-top: -1px;
    3138    background-color: var(--audit-test-header-background-color);
    32     border-top: 1px solid var(--border-color);
    3339    -webkit-backdrop-filter: blur(20px);
    3440}
    3541
    36 .content-view-container > .content-view.audit-test-case > section > :not(.message-text-view):first-child {
     42.content-view-container > .content-view.audit-test-case.manager-editing > header h1 > img {
     43    width: 0.75em;
     44    min-width: 12px;
     45    height: 0.75em;
     46    min-height: 12px;
     47    margin: 0.125em;
     48    margin-inline-end: 0.375em;
     49}
     50
     51.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view, .editor):first-child {
    3752    margin-top: var(--audit-test-vertical-space);
     53}
     54
     55.content-view-container > .content-view.audit-test-case > section {
     56    overflow-y: scroll;
     57}
     58
     59.content-view-container > .content-view.audit-test-case > section,
     60.content-view-container > .content-view.audit-test-case > section :is(.editor, .CodeMirror) {
     61    height: 100%;
    3862}
    3963
     
    5377    min-height: 16px;
    5478    -webkit-margin-end: 0.25em;
     79}
     80
     81.content-view.audit-test-case.manager-editing.disabled:not(.editable) > header h1 > img {
     82    opacity: 0.3;
     83    pointer-events: none;
    5584}
    5685
     
    84113}
    85114
    86 .content-view.audit-test-case > section > :not(.message-text-view) {
     115.content-view.audit-test-case > section > :not(.message-text-view, .editor) {
    87116    margin-right: var(--audit-test-horizontal-space);
    88117    margin-left: var(--audit-test-horizontal-space);
    89118}
    90119
    91 .content-view.audit-test-case > section > :not(.message-text-view):last-child {
     120.content-view.audit-test-case > section > :not(.message-text-view, .editor):last-child {
    92121    margin-bottom: var(--audit-test-vertical-space);
    93122}
    94123
    95 .content-view.audit-test-case > section > :not(.message-text-view) + :not(.message-text-view) {
     124.content-view.audit-test-case > section > :not(.message-text-view, .editor) + :not(.message-text-view, .editor) {
    96125    margin-top: var(--audit-test-vertical-space);
    97126}
     
    132161}
    133162
    134 .content-view.audit-test-case > section .CodeMirror {
    135     width: 100%;
    136     height: auto;
    137 }
    138 
    139163.content-view.audit-test-case > section .mark {
    140164    background-color: hsla(53, 83%, 53%, 0.2);
    141165    border-bottom: 1px solid hsl(47, 82%, 60%);
    142166}
     167
     168@media (prefers-color-scheme: dark) {
     169    .content-view.audit-test-case.manager-editing > header h1 > img {
     170        filter: var(--filter-invert);
     171    }
     172}
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js

    r253757 r266317  
    4848        informationContainer.classList.add("information");
    4949
    50         let nameElement = informationContainer.appendChild(document.createElement("h1"));
    51 
    52         this._resultImageElement = nameElement.appendChild(document.createElement("img"));
    53 
    54         nameElement.appendChild(document.createTextNode(this.representedObject.name));
    55 
    56         if (this.representedObject.description) {
    57             let descriptionElement = informationContainer.appendChild(document.createElement("p"));
    58             descriptionElement.textContent = this.representedObject.description;
    59         }
     50        let nameContainer = informationContainer.appendChild(document.createElement("h1"));
     51
     52        this._resultImageElement = nameContainer.appendChild(document.createElement("img"));
     53
     54        nameContainer.appendChild(this.createNameElement("span"));
     55
     56        informationContainer.appendChild(this.createDescriptionElement("p"));
     57
     58        if (this.representedObject instanceof WI.AuditTestCase)
     59            informationContainer.appendChild(this.createControlsTableElement());
    6060
    6161        this._metadataElement = this.headerView.element.appendChild(document.createElement("div"));
     
    7070        super.layout();
    7171
     72        this._metadataElement.removeChildren();
     73        this.contentView.element.removeChildren();
     74
     75        if (WI.auditManager.editing) {
     76            this._resultImageElement.src = "Images/Pencil.svg";
     77            this._resultImageElement.title = WI.UIString("Editing audit", "Editing Audit @ Audit Tab - Test Case", "Title of icon indiciating that the selected audit is being edited.");
     78
     79            let testEditorElement = this.contentView.element.appendChild(document.createElement("div"));
     80            testEditorElement.className = "editor";
     81
     82            // Give the rest of the view a chance to load.
     83            setTimeout(() => {
     84                let testCodeMirror = WI.CodeMirrorEditor.create(testEditorElement, {
     85                    autoCloseBrackets: true,
     86                    lineNumbers: true,
     87                    lineWrapping: true,
     88                    matchBrackets: true,
     89                    mode: "text/javascript",
     90                    readOnly: this.representedObject.editable ? false : "nocursor",
     91                    styleSelectedText: true,
     92                    value: this.representedObject.test,
     93                });
     94
     95                if (this.representedObject.editable) {
     96                    testCodeMirror.on("blur", (event) => {
     97                        this.representedObject.test = testCodeMirror.getValue().trim();
     98                    });
     99                }
     100            });
     101            return;
     102        }
     103
    72104        this._resultImageElement.src = "Images/AuditTestNoResult.svg";
    73         this._metadataElement.removeChildren();
    74 
    75         this.contentView.element.removeChildren();
     105        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.");
    76106
    77107        let result = this.representedObject.result;
     
    87117        }
    88118
    89         if (result.didError)
     119        if (result.didError) {
    90120            this._resultImageElement.src = "Images/AuditTestError.svg";
    91         else if (result.didFail)
     121            this._resultImageElement.title = WI.UIString("Error", "Error @ Audit Tab - Test Case", "Title of icon indicating that the selected audit threw an error.");
     122        } else if (result.didFail) {
    92123            this._resultImageElement.src = "Images/AuditTestFail.svg";
    93         else if (result.didWarn)
     124            this._resultImageElement.title = WI.UIString("Fail", "Fail @ Audit Tab - Test Case", "Title of icon indicating that the selected audit failed.");
     125        } else if (result.didWarn) {
    94126            this._resultImageElement.src = "Images/AuditTestWarn.svg";
    95         else if (result.didPass)
     127            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).");
     128        } else if (result.didPass) {
    96129            this._resultImageElement.src = "Images/AuditTestPass.svg";
    97         else if (result.unsupported)
     130            this._resultImageElement.title = WI.UIString("Pass", "Pass @ Audit Tab - Test Case", "Title of icon indicating that the selected audit passed with no issues.");
     131        } else if (result.unsupported) {
    98132            this._resultImageElement.src = "Images/AuditTestUnsupported.svg";
     133            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).");
     134        }
    99135
    100136        let metadata = result.metadata;
     
    300336            let spinner = new WI.IndeterminateProgressSpinner;
    301337            this.placeholderElement.appendChild(spinner.element);
     338
     339            let stopAuditNavigationItem = new WI.ButtonNavigationItem("stop-audit", WI.UIString("Stop"), "Images/AuditStop.svg", 13, 13);
     340            stopAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
     341            stopAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, (event) => {
     342                WI.auditManager.stop();
     343            }, WI.auditManager);
     344
     345            let stopAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop running."), stopAuditNavigationItem);
     346            this.placeholderElement.appendChild(stopAuditHelpElement);
     347
     348            this.placeholderElement.appendChild(WI.createReferencePageLink("audit-tab"));
    302349        }
    303350
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.css

    r265118 r266317  
    4949}
    5050
     51.content-view.audit-test:is(.unsupported, .disabled):not(.manager-editing) {
     52    display: none;
     53}
     54
    5155.content-view.audit-test h1 {
    5256    margin: 0;
     57}
     58
     59.content-view.audit-test.manager-editing .editor:not(:empty) {
     60    width: 100%;
     61}
     62
     63.content-view.audit-test.manager-editing :is(.content-view.audit-test, header) .editor:not(:empty) {
     64    border: 1px solid var(--border-color);
     65}
     66
     67.content-view.audit-test .CodeMirror {
     68    width: 100%;
     69    height: auto;
    5370}
    5471
     
    7188}
    7289
     90.content-view.audit-test > header :is(.name, .description):not([contenteditable]) {
     91    padding: 1px;
     92    outline: none;
     93}
     94
     95.content-view.audit-test.manager-editing > header :is(.name, .description)[contenteditable] {
     96    border: 1px solid var(--border-color);
     97    -webkit-user-select: text;
     98    outline-offset: var(--focus-ring-outline-offset);
     99}
     100
     101.content-view.audit-test.manager-editing > header .name[contenteditable]:empty {
     102    border-color: var(--error-text-color);
     103}
     104
     105.content-view.audit-test.manager-editing > header .name[contenteditable]:empty:before {
     106    content: attr(data-name);
     107    color: var(--text-color-tertiary);
     108}
     109
     110.content-view.audit-test.manager-editing > header .description[contenteditable]:empty:before {
     111    content: "description";
     112    color: var(--text-color-tertiary);
     113}
     114
     115.content-view.audit-test:not(.manager-editing) > header .description:empty,
     116.content-view.audit-test:not(.manager-editing) > header table.controls {
     117    display: none;
     118}
     119
     120.content-view.audit-test > header table.controls,
     121.content-view.audit-test > header table.controls > tr > td {
     122    width: 100%;
     123}
     124
     125.content-view.audit-test > header table.controls > tr > th {
     126    text-align: end;
     127    vertical-align: top;
     128    line-height: 2em;
     129    color: hsl(0, 0%, 34%);
     130}
     131
     132.content-view.audit-test > header table.controls > tr.supports input[type="number"] {
     133    text-align: end;
     134}
     135
     136.content-view.audit-test > header table.controls > tr.supports .warning {
     137    margin-inline-start: 4px;
     138    color: var(--text-color-secondary);
     139}
     140
     141.content-view.audit-test > header table.controls > tr.supports .warning:not(:empty)::before {
     142    display: inline-block;
     143    width: 1em;
     144    margin-inline-end: 2px;
     145    vertical-align: -1px;
     146    content: url(../Images/Warning.svg);
     147}
     148
     149.content-view.audit-test > header table.controls > tr.setup .editor {
     150    margin-top: 2px;
     151}
     152
    73153.content-view.audit-test .audit-test.filtered,
    74154.content-view.audit-test .audit-test .message-text-view {
     
    82162.content-view.audit-test > section > .message-text-view {
    83163    background-color: var(--background-color-content);
     164}
     165
     166.content-view.audit-test > section > .message-text-view > :is(progress, .indeterminate-progress-spinner) {
     167    margin-bottom: 8px;
    84168}
    85169
     
    101185        --audit-test-header-background-color: hsla(0, 0%, 23%, 0.7);
    102186    }
     187
     188    .content-view.audit-test > header table.controls > tr > th {
     189        color: var(--text-color-secondary);
     190    }
    103191}
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js

    r245914 r266317  
    3636
    3737        this.element.classList.add("audit-test");
    38 
    39         this._exportButtonNavigationItem = new WI.ButtonNavigationItem("audit-export", WI.UIString("Export"), "Images/Export.svg", 15, 15);
    40         this._exportButtonNavigationItem.tooltip = WI.UIString("Export result (%s)").format(WI.saveKeyboardShortcut.displayName);
    41         this._exportButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
    42         this._exportButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
    43         this._exportButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleExportButtonNavigationItemClicked, this);
    44         this._updateExportButtonNavigationItemState();
     38        if (this.representedObject.editable)
     39            this.element.classList.add("editable");
     40
     41        if (this.representedObject instanceof WI.AuditTestBase) {
     42            this._exportTestButtonNavigationItem = new WI.ButtonNavigationItem("audit-export-test", WI.UIString("Export Audit"), "Images/Export.svg", 15, 15);
     43            this._exportTestButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
     44            this._exportTestButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
     45            this._exportTestButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleExportTestButtonNavigationItemClicked, this);
     46        }
     47
     48        this._exportResultButtonNavigationItem = new WI.ButtonNavigationItem("audit-export-result", WI.UIString("Export Result"), "Images/Export.svg", 15, 15);
     49        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);
     50        this._exportResultButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
     51        this._exportResultButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
     52        this._exportResultButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleExportResultButtonNavigationItemClicked, this);
     53
     54        this._updateExportNavigationItems();
    4555
    4656        this._headerView = new WI.View(document.createElement("header"));
     
    4858        this._placeholderElement = null;
    4959
     60        this._cachedName = this.representedObject.name;
     61        this._nameElement = null;
     62
     63        this._descriptionElement = null;
     64
     65        this._supportsInputElement = null;
     66        this._supportsWarningElement = null;
     67
    5068        this._shownResult = null;
    5169    }
     
    5573    get navigationItems()
    5674    {
    57         return [this._exportButtonNavigationItem];
     75        let navigationItems = [];
     76        if (this._exportTestButtonNavigationItem)
     77            navigationItems.push(this._exportTestButtonNavigationItem);
     78        navigationItems.push(this._exportResultButtonNavigationItem);
     79        return navigationItems;
    5880    }
    5981
     
    6587    get supportsSave()
    6688    {
    67         return !!this.representedObject.result;
     89        return !WI.auditManager.editing && !!this.representedObject.result;
    6890    }
    6991
     
    80102    }
    81103
     104    createNameElement(tagName)
     105    {
     106        console.assert(!this._nameElement);
     107
     108        this._nameElement = document.createElement(tagName);
     109        this._nameElement.textContent = this.representedObject.name;
     110        this._nameElement.className = "name";
     111
     112        if (this.representedObject.editable) {
     113            this._nameElement.spellcheck = false;
     114
     115            this._nameElement.addEventListener("keydown", (event) => {
     116                this._handleEditorKeydown(event, this._descriptionElement);
     117            });
     118
     119            this._nameElement.addEventListener("input", (event) => {
     120                console.assert(WI.auditManager.editing);
     121
     122                let name = this._nameElement.textContent;
     123                if (!name.trim()) {
     124                    name = this._cachedName;
     125                    this._nameElement.removeChildren();
     126                }
     127                this.representedObject.name = name;
     128            });
     129        }
     130
     131        return this._nameElement;
     132    }
     133
     134    createDescriptionElement(tagName)
     135    {
     136        console.assert(!this._descriptionElement);
     137
     138        this._descriptionElement = document.createElement(tagName);
     139        this._descriptionElement.textContent = this.representedObject.description;
     140        this._descriptionElement.className = "description";
     141
     142        if (this.representedObject.editable) {
     143            this._descriptionElement.spellcheck = false;
     144
     145            this._descriptionElement.addEventListener("keydown", (event) => {
     146                this._handleEditorKeydown(event, this._supportsInputElement);
     147            });
     148
     149            this._descriptionElement.addEventListener("input", (event) => {
     150                console.assert(WI.auditManager.editing);
     151
     152                let description = this._descriptionElement.textContent;
     153                if (!description.trim()) {
     154                    description = "";
     155                    this._descriptionElement.removeChildren();
     156                }
     157                this.representedObject.description = description;
     158            });
     159        }
     160
     161        return this._descriptionElement;
     162    }
     163
     164    createControlsTableElement()
     165    {
     166        console.assert(this.representedObject instanceof WI.AuditTestBase);
     167        console.assert(!this._supportsInputElement);
     168        console.assert(!this._supportsWarningElement);
     169
     170        let controlsTableElement = document.createElement("table");
     171        controlsTableElement.className = "controls";
     172
     173        let supportsRowElement = controlsTableElement.appendChild(document.createElement("tr"));
     174        supportsRowElement.className = "supports";
     175
     176        let supportsHeaderElement = supportsRowElement.appendChild(document.createElement("th"));
     177        supportsHeaderElement.textContent = WI.unlocalizedString("supports");
     178
     179        let supportsDataElement = supportsRowElement.appendChild(document.createElement("td"));
     180
     181        this._supportsInputElement = supportsDataElement.appendChild(document.createElement("input"));
     182        this._supportsInputElement.type = "number";
     183        this._supportsInputElement.disabled = !this.representedObject.editable;
     184        this._supportsInputElement.min = 0;
     185        this._supportsInputElement.placeholder = Math.min(WI.AuditTestBase.Version, InspectorBackend.hasDomain("Audit") ? InspectorBackend.getVersion("Audit") : Infinity);
     186        if (!isNaN(this.representedObject.supports))
     187            this._supportsInputElement.value = this.representedObject.supports;
     188
     189        if (this.representedObject.editable) {
     190            this._supportsInputElement.addEventListener("keydown", (event) => {
     191                this._handleEditorKeydown(event, this._setupEditorElement);
     192            });
     193        }
     194
     195        this._supportsWarningElement = supportsDataElement.appendChild(document.createElement("span"));
     196        this._supportsWarningElement.className = "warning";
     197
     198        if (this.representedObject.topLevelTest === this.representedObject) {
     199            let setupRowElement = controlsTableElement.appendChild(document.createElement("tr"));
     200            setupRowElement.className = "setup";
     201
     202            let setupHeaderElement = setupRowElement.appendChild(document.createElement("th"));
     203            setupHeaderElement.textContent = WI.unlocalizedString("setup");
     204
     205            let setupDataElement = setupRowElement.appendChild(document.createElement("td"));
     206
     207            this._setupEditorElement = setupDataElement.appendChild(document.createElement("div"));
     208        }
     209
     210        if (this.representedObject.editable) {
     211            this._supportsInputElement.addEventListener("input", (event) => {
     212                this.representedObject.supports = parseInt(this._supportsInputElement.value);
     213
     214                this._updateSupportsInputState();
     215            });
     216        }
     217
     218        return controlsTableElement;
     219    }
     220
    82221    initialLayout()
    83222    {
     
    92231        super.layout();
    93232
     233        if (this.representedObject instanceof WI.AuditTestBase) {
     234            this.element.classList.toggle("unsupported", !this.representedObject.supported);
     235            this.element.classList.toggle("disabled", this.representedObject.disabled);
     236            this.element.classList.toggle("manager-editing", WI.auditManager.editing);
     237
     238            if (this.representedObject.editable) {
     239                let contentEditable = WI.auditManager.editing ? "plaintext-only" : "inherit";
     240                this._nameElement.contentEditable = contentEditable;
     241                this._descriptionElement.contentEditable = contentEditable;
     242            }
     243
     244            if (WI.auditManager.editing) {
     245                this._cachedName = this.representedObject.name;
     246                this._nameElement.dataset.name = this._cachedName;
     247
     248                this._updateSupportsInputState();
     249                this._createSetupEditor();
     250            } else {
     251                this._nameElement.textContent ||= this._cachedName;
     252
     253                this._setupEditorElement?.removeChildren();
     254            }
     255        }
     256
    94257        this.hidePlaceholder();
    95         this._updateExportButtonNavigationItemState();
     258        this._updateExportNavigationItems();
    96259    }
    97260
     
    102265        if (this.representedObject instanceof WI.AuditTestBase) {
    103266            this.representedObject.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestChanged, this);
     267            this.representedObject.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
    104268            this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestChanged, this);
    105269            this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultChanged, this.handleResultChanged, this);
    106270            this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestChanged, this);
    107271            this.representedObject.addEventListener(WI.AuditTestBase.Event.Stopping, this._handleTestChanged, this);
     272            this.representedObject.addEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
     273
     274            WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleEditingChanged, this);
    108275        }
    109276    }
     
    111278    hidden()
    112279    {
    113         if (this.representedObject instanceof WI.AuditTestBase)
    114             this.representedObject.removeEventListener(null, null, this);
     280        if (this.representedObject instanceof WI.AuditTestBase) {
     281            this.representedObject.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestChanged, this);
     282            this.representedObject.removeEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this);
     283            this.representedObject.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestChanged, this);
     284            this.representedObject.removeEventListener(WI.AuditTestBase.Event.ResultChanged, this.handleResultChanged, this);
     285            this.representedObject.removeEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestChanged, this);
     286            this.representedObject.removeEventListener(WI.AuditTestBase.Event.Stopping, this._handleTestChanged, this);
     287            this.representedObject.removeEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
     288
     289            WI.auditManager.removeEventListener(WI.AuditManager.Event.EditingChanged, this._handleEditingChanged, this);
     290        }
    115291
    116292        super.hidden();
     
    121297        // Overridden by sub-classes.
    122298
    123         this.needsLayout();
     299        if (!WI.auditManager.editing)
     300            this.needsLayout();
    124301    }
    125302
     
    153330            let spinner = new WI.IndeterminateProgressSpinner;
    154331            this.placeholderElement.appendChild(spinner.element);
     332
     333            this.placeholderElement.appendChild(WI.createReferencePageLink("audit-tab"));
    155334        }
    156335
     
    170349            });
    171350
    172             let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to start running the audit"), startNavigationItem);
     351            let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to start running the audit."), startNavigationItem);
    173352            this.placeholderElement.appendChild(importHelpElement);
     353
     354            this.placeholderElement.appendChild(WI.createReferencePageLink("audit-tab"));
    174355        }
    175356
     
    204385            this.placeholderElement = WI.createMessageTextView(message.format(this.representedObject.name), result.didError);
    205386            this.placeholderElement.__placeholderNoResultData = true;
     387
     388            this.placeholderElement.appendChild(WI.createReferencePageLink("audit-tab"));
    206389        }
    207390
     
    221404                this.needsLayout();
    222405            });
     406
     407            this.placeholderElement.appendChild(WI.createReferencePageLink("audit-tab"));
    223408        }
    224409
     
    279464    }
    280465
    281     _updateExportButtonNavigationItemState()
    282     {
    283         this._exportButtonNavigationItem.enabled = !!this.representedObject.result;
     466    _updateExportNavigationItems()
     467    {
     468        if (this._exportTestButtonNavigationItem)
     469            this._exportTestButtonNavigationItem.enabled = !WI.auditManager.editing;
     470
     471        this._exportResultButtonNavigationItem.enabled = !WI.auditManager.editing && this.representedObject.result;
     472    }
     473
     474    _updateSupportsInputState()
     475    {
     476        console.assert(WI.auditManager.editing);
     477
     478        this._supportsInputElement.autosize(4);
     479
     480        this._supportsWarningElement.removeChildren();
     481        if (this.representedObject.supports > WI.AuditTestBase.Version)
     482            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.");
     483        else if (InspectorBackend.hasDomain("Audit") && this._supports > InspectorBackend.getVersion("Audit"))
     484            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.");
     485    }
     486
     487    _createSetupEditor()
     488    {
     489        if (!this._setupEditorElement)
     490            return;
     491
     492        let setupEditorElement = document.createElement(this._setupEditorElement.nodeName);
     493        setupEditorElement.className = "editor";
     494
     495        // Give the rest of the view a chance to load.
     496        setTimeout(() => {
     497            let setupCodeMirror = WI.CodeMirrorEditor.create(setupEditorElement, {
     498                autoCloseBrackets: true,
     499                lineNumbers: true,
     500                lineWrapping: true,
     501                matchBrackets: true,
     502                mode: "text/javascript",
     503                readOnly: this.representedObject.editable ? false : "nocursor",
     504                styleSelectedText: true,
     505                value: this.representedObject.setup,
     506            });
     507
     508            if (this.representedObject.editable) {
     509                setupCodeMirror.on("blur", (event) => {
     510                    this.representedObject.setup = setupCodeMirror.getValue().trim();
     511                });
     512            }
     513        });
     514
     515        this._setupEditorElement.parentNode.replaceChild(setupEditorElement, this._setupEditorElement);
     516        this._setupEditorElement = setupEditorElement;
    284517    }
    285518
     
    290523    }
    291524
    292     _handleExportButtonNavigationItemClicked(event)
     525    _handleEditorKeydown(event, nextEditor)
     526    {
     527        console.assert(WI.auditManager.editing);
     528
     529        switch (event.keyCode) {
     530        case WI.KeyboardShortcut.Key.Enter.keyCode:
     531            if (nextEditor) {
     532                nextEditor.focus();
     533                break;
     534            }
     535            // fallthrough
     536
     537        case WI.KeyboardShortcut.Key.Escape.keyCode:
     538            event.target.blur();
     539            break;
     540
     541        default:
     542            return;
     543        }
     544
     545        event.preventDefault();
     546    }
     547
     548    _handleExportTestButtonNavigationItemClicked(event)
     549    {
     550        WI.auditManager.export(this.representedObject);
     551    }
     552
     553    _handleExportResultButtonNavigationItemClicked(event)
    293554    {
    294555        this._exportResult();
     
    299560        this.needsLayout();
    300561    }
     562
     563    _handleTestDisabledChanged(event)
     564    {
     565        console.assert(WI.auditManager.editing);
     566
     567        this.element.classList.toggle("disabled", this.representedObject.disabled);
     568    }
     569
     570    _handleTestSupportedChanged(event)
     571    {
     572        console.assert(WI.auditManager.editing);
     573
     574        this.element.classList.toggle("unsupported", !this.representedObject.supported);
     575    }
     576
     577    _handleEditingChanged(event)
     578    {
     579        this.needsLayout();
     580
     581        this._updateExportNavigationItems();
     582    }
    301583};
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.css

    r258057 r266317  
    2828}
    2929
     30.content-view.audit-test-group > section > .audit-test-group > header {
     31    margin-top: -1px;
     32    border-top: 1px solid var(--border-color);
     33}
     34
    3035.content-view.audit-test-group > header {
    31     margin-top: -1px;
    3236    -webkit-padding-end: var(--audit-test-horizontal-space);
    33     border-top: 1px solid var(--border-color);
    3437}
    3538
     
    4548.content-view.audit-test-group.contains-test-case > header {
    4649    position: sticky;
    47     top: -1px;
     50    top: 0;
    4851    z-index: var(--z-index-header);
    4952    background-color: var(--audit-test-header-background-color);
     
    5154}
    5255
    53 .content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case {
     56.content-view.audit-test-group > section > .audit-test-group.contains-test-case > header {
     57    top: -1px;
     58}
     59
     60.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case,
     61.content-view.audit-test-group + .content-view.audit-test-case {
    5462    border-top: 1px solid var(--border-color);
    5563}
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.js

    r249118 r266317  
    3333
    3434        this.element.classList.add("audit-test-group");
    35         this.element.classList.toggle("contains-test-case", this._subobjects().some((test) => test instanceof WI.AuditTestCase || test instanceof WI.AuditTestCaseResult));
    36         this.element.classList.toggle("contains-test-group", this._subobjects().some((test) => test instanceof WI.AuditTestGroup || test instanceof WI.AuditTestGroupResult));
    3735
    3836        this._levelScopeBar = null;
     37
     38        this._viewForSubobject = new Map;
     39    }
     40
     41    // Popover delegate
     42
     43    willDismissPopover(popover)
     44    {
     45        console.assert(popover instanceof WI.CreateAuditPopover, popover);
     46
     47        let audit = popover.audit;
     48        if (!audit) {
     49            InspectorFrontendHost.beep();
     50            return;
     51        }
     52
     53        this.representedObject.addTest(audit);
    3954    }
    4055
    4156    // Protected
     57
     58    createControlsTableElement()
     59    {
     60        let controlsTableElement = super.createControlsTableElement();
     61
     62        let actionsRowElement = controlsTableElement.appendChild(document.createElement("tr"));
     63        actionsRowElement.className = "actions";
     64
     65        let actionsHeaderElement = controlsTableElement.appendChild(document.createElement("th"));
     66        let actionsDataElement = controlsTableElement.appendChild(document.createElement("td"));
     67
     68        let addTestCaseButtonElement = actionsDataElement.appendChild(document.createElement("button"));
     69        addTestCaseButtonElement.disabled = !this.representedObject.editable;
     70        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.");
     71        addTestCaseButtonElement.addEventListener("click", (event) => {
     72            console.assert(WI.auditManager.editing);
     73
     74            let popover = new WI.CreateAuditPopover(this);
     75            popover.show(addTestCaseButtonElement, [WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X, WI.RectEdge.MIN_X]);
     76        });
     77
     78        return controlsTableElement;
     79    }
    4280
    4381    initialLayout()
     
    4886        informationContainer.classList.add("information");
    4987
    50         let nameElement = informationContainer.appendChild(document.createElement("h1"));
    51         nameElement.textContent = this.representedObject.name;
    52 
    53         if (this.representedObject.description) {
    54             let descriptionElement = informationContainer.appendChild(document.createElement("p"));
    55             descriptionElement.textContent = this.representedObject.description;
    56         }
     88        let nameContainer = informationContainer.appendChild(document.createElement("h1"));
     89
     90        nameContainer.appendChild(this.createNameElement("span"));
     91
     92        informationContainer.appendChild(this.createDescriptionElement("p"));
     93
     94        if (this.representedObject instanceof WI.AuditTestGroup)
     95            informationContainer.appendChild(this.createControlsTableElement());
    5796
    5897        this._levelNavigationBar = new WI.NavigationBar(document.createElement("nav"));
     
    70109            return a;
    71110        });
     111
     112        this._updateClassList();
    72113    }
    73114
     
    78119
    79120        super.layout();
     121
     122        if (WI.auditManager.editing) {
     123            if (this._levelScopeBar) {
     124                this._levelNavigationBar.removeNavigationItem(this._levelScopeBar);
     125                this._levelScopeBar = null;
     126            }
     127
     128            this._percentageContainer.hidden = true;
     129
     130            this.resetFilter();
     131            return;
     132        }
    80133
    81134        let result = this.representedObject.result;
     
    149202            this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this);
    150203            this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
    151         }
    152 
    153         for (let subobject of this._subobjects()) {
    154             if (subobject instanceof WI.AuditTestBase && subobject.disabled)
    155                 continue;
    156 
    157             let view = WI.ContentView.contentViewForRepresentedObject(subobject);
    158             this.contentView.addSubview(view);
    159             view.shown();
    160         }
     204
     205            if (this.representedObject.editable) {
     206                this.representedObject.addEventListener(WI.AuditTestGroup.Event.TestAdded, this._handleTestGroupTestAdded, this);
     207                this.representedObject.addEventListener(WI.AuditTestGroup.Event.TestRemoved, this._handleTestGroupTestRemoved, this);
     208            }
     209        }
     210
     211        console.assert(!this._viewForSubobject.size);
     212        for (let subobject of this._subobjects())
     213            this._addTest(subobject);
    161214    }
    162215
    163216    hidden()
    164217    {
    165         for (let view of this.contentView.subviews)
     218        if (this.representedObject instanceof WI.AuditTestGroup) {
     219            this.representedObject.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this);
     220            this.representedObject.removeEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
     221
     222            if (this.representedObject.editable) {
     223                this.representedObject.removeEventListener(WI.AuditTestGroup.Event.TestAdded, this._handleTestGroupTestAdded, this);
     224                this.representedObject.removeEventListener(WI.AuditTestGroup.Event.TestRemoved, this._handleTestGroupTestRemoved, this);
     225            }
     226        }
     227
     228        for (let view of this._viewForSubobject.values())
    166229            view.hidden();
    167 
    168230        this.contentView.removeAllSubviews();
     231        this._viewForSubobject.clear();
    169232
    170233        super.hidden();
     
    197260            this.placeholderElement.__progress.value = 0;
    198261            this.placeholderElement.appendChild(this.placeholderElement.__progress);
     262
     263            let stopAuditNavigationItem = new WI.ButtonNavigationItem("stop-audit", WI.UIString("Stop"), "Images/AuditStop.svg", 13, 13);
     264            stopAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText;
     265            stopAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, (event) => {
     266                WI.auditManager.stop();
     267            }, WI.auditManager);
     268
     269            let stopAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop running."), stopAuditNavigationItem);
     270            this.placeholderElement.appendChild(stopAuditHelpElement);
     271
     272            this.placeholderElement.appendChild(WI.createReferencePageLink("audit-tab"));
    199273        }
    200274
     
    216290    }
    217291
     292    _updateClassList()
     293    {
     294        let subobjects = this._subobjects();
     295        let containsTestGroup = subobjects.some((test) => test instanceof WI.AuditTestGroup || test instanceof WI.AuditTestGroupResult);
     296        this.element.classList.toggle("contains-test-group", containsTestGroup);
     297        this.element.classList.toggle("contains-test-case", !containsTestGroup && subobjects.some((test) => test instanceof WI.AuditTestCase || test instanceof WI.AuditTestCaseResult));
     298    }
     299
    218300    _updateLevelScopeBar(levels)
    219301    {
     
    224306            item.selected = levels.includes(item.id);
    225307
    226         for (let view of this.contentView.subviews) {
     308        for (let view of this._viewForSubobject.values()) {
    227309            if (view instanceof WI.AuditTestGroupContentView)
    228310                view._updateLevelScopeBar(levels);
    229311        }
     312    }
     313
     314    _addTest(test)
     315    {
     316        console.assert(!this._viewForSubobject.has(test));
     317
     318        let view = WI.ContentView.contentViewForRepresentedObject(test);
     319        this.contentView.addSubview(view);
     320        view.shown();
     321
     322        this._viewForSubobject.set(test, view);
    230323    }
    231324
     
    243336    }
    244337
     338    _handleTestGroupTestAdded(event)
     339    {
     340        console.assert(WI.auditManager.editing);
     341
     342        let {test} = event.data;
     343
     344        this._addTest(test);
     345
     346        this._updateClassList();
     347    }
     348
     349    _handleTestGroupTestRemoved(event)
     350    {
     351        console.assert(WI.auditManager.editing);
     352
     353        let {test} = event.data;
     354
     355        let view = this._viewForSubobject.get(test);
     356        console.assert(view);
     357
     358        view.hidden();
     359        this.contentView.removeSubview(view);
     360
     361        this._updateClassList();
     362    }
     363
    245364    _handleLevelScopeBarSelectionChanged(event)
    246365    {
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.css

    r256774 r266317  
    3636}
    3737
    38 .tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active) > .status:hover > img {
     38.tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img {
    3939    width: 75%;
    4040    height: 75%;
     
    4343}
    4444
    45 .tree-outline .item.audit > .status:not(:hover) > img.show-on-hover,
    46 .tree-outline .item.audit.test-group.expanded:not(.unsupported, .editing-audits) > .status:not(:hover) {
     45body: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,
     46body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit.test-case.selected > .status > .indeterminate-progress-spinner,
     47body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit.test-group.selected > .status > progress {
     48    filter: var(--filter-invert);
     49}
     50
     51.tree-outline .item.audit:not(:hover) > .status > img.show-on-hover,
     52.tree-outline .item.audit.test-group.expanded:not(.unsupported, .editing-audits):not(:hover) > .status {
    4753    opacity: 0;
    4854}
    4955
    5056.tree-outline .item.audit.manager-active > .status > img.show-on-hover,
    51 .tree-outline .item.audit.test-group.expanded:not(.editing-audits) > .status:hover > :not(img),
     57.tree-outline .item.audit.test-group.expanded:not(.editing-audits):hover > .status > :not(img),
    5258.tree-outline .item.audit.test-group-result.expanded > .status,
    5359.tree-outline .item.audit.unsupported + .children .item.audit.unsupported  > .status > img {
     
    103109
    104110@media (prefers-color-scheme: dark) {
     111    .tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img {
     112        filter: var(--filter-invert) brightness(90%);
     113    }
     114
    105115    .audit.test-case .icon {
    106116        content: url(../Images/TypeIcons.svg#AuditTestCase-dark);
  • trunk/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js

    r245914 r266317  
    5252
    5353        if (isTestGroup)
    54             this._expandedSetting = new WI.Setting(`audit-tree-element-${this.representedObject.name}-expanded`, false);
     54            this._expandedSetting = new WI.Setting(WI.AuditTreeElement.expandedSettingKey(this.representedObject.name), false);
     55    }
     56
     57    // Static
     58
     59    static expandedSettingKey(name)
     60    {
     61        return `audit-tree-element-${name}-expanded`;
    5562    }
    5663
     
    6976            else if (this.representedObject instanceof WI.AuditTestGroup)
    7077                this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this);
     78
     79            if (this.representedObject.editable) {
     80                this.representedObject.addEventListener(WI.AuditTestBase.Event.NameChanged, this._handleTestNameChanged, this);
     81                this.representedObject.addEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this);
     82
     83                if (this.representedObject instanceof WI.AuditTestGroup)
     84                    this.representedObject.addEventListener(WI.AuditTestGroup.Event.TestAdded, this._handleTestGroupTestAdded, this);
     85            }
    7186
    7287            WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleManagerEditingChanged, this);
     
    130145            return false;
    131146
    132         if (!(this.parent instanceof WI.TreeOutline))
    133             return false;
    134 
    135147        if (!WI.auditManager.editing)
    136148            return false;
    137149
    138         WI.auditManager.removeTest(this.representedObject);
     150        this.representedObject.remove();
    139151
    140152        return true;
    141153    }
    142154
     155    canSelectOnMouseDown(event)
     156    {
     157        if (this.representedObject instanceof WI.AuditTestBase && this.representedObject.supported && this.status.contains(event.target))
     158            return false;
     159
     160        return super.canSelectOnMouseDown(event);
     161    }
     162
    143163    populateContextMenu(contextMenu, event)
    144164    {
    145         if (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive) {
    146             contextMenu.appendItem(WI.UIString("Start"), (event) => {
    147                 this._start();
    148             });
    149         }
     165        let isTest = this.representedObject instanceof WI.AuditTestBase;
    150166
    151167        contextMenu.appendSeparator();
    152168
    153         if (this.representedObject instanceof WI.AuditTestBase) {
    154             contextMenu.appendItem(WI.UIString("Export Test"), (event) => {
    155                 WI.auditManager.export(this.representedObject);
    156             });
    157         }
    158 
    159         if (this.representedObject.result) {
    160             contextMenu.appendItem(WI.UIString("Export Result"), (event) => {
     169        if (WI.auditManager.editing) {
     170            if (isTest) {
     171                if (this.representedObject.supported) {
     172                    contextMenu.appendItem(this.representedObject.disabled ? WI.UIString("Enable Audit") : WI.UIString("Disable Audit"), () => {
     173                        this.representedObject.disabled = !this.representedObject.disabled;
     174                    });
     175                }
     176
     177                contextMenu.appendItem(WI.UIString("Duplicate Audit"), async () => {
     178                    let audit = await this.representedObject.clone();
     179                    WI.auditManager.addTest(audit);
     180                });
     181
     182                if (this.representedObject.editable) {
     183                    contextMenu.appendItem(WI.UIString("Delete Audit"), () => {
     184                        this.representedObject.remove();
     185                    });
     186                }
     187            }
     188        } else {
     189            if (isTest) {
     190                contextMenu.appendItem(WI.UIString("Start Audit"), () => {
     191                    this._start();
     192                }, WI.auditManager.runningState !== WI.AuditManager.RunningState.Inactive);
     193
     194                contextMenu.appendSeparator();
     195
     196                contextMenu.appendItem(WI.UIString("Export Audit"), () => {
     197                    WI.auditManager.export(this.representedObject);
     198                });
     199            }
     200
     201            contextMenu.appendItem(WI.UIString("Export Result"), () => {
    161202                WI.auditManager.export(this.representedObject.result);
    162             });
     203            }, !this.representedObject.result);
     204
     205            if (isTest && this.representedObject.editable) {
     206                contextMenu.appendSeparator();
     207
     208                contextMenu.appendItem(WI.UIString("Edit Audit"), () => {
     209                    WI.auditManager.editing = true;
     210                    WI.showRepresentedObject(this.representedObject);
     211                });
     212            }
    163213        }
    164214
     
    316366    }
    317367
     368    _handleTestNameChanged(event)
     369    {
     370        this.mainTitle = this.representedObject.name;
     371
     372        if (this.representedObject instanceof WI.AuditTestGroup)
     373            this._expandedSetting = new WI.Setting(WI.AuditTreeElement.expandedSettingKey(this.representedObject.name), !!WI.Setting.migrateValue(WI.AuditTreeElement.expandedSettingKey(event.data.oldName)));
     374    }
     375
     376    _handleTestSupportedChanged(event)
     377    {
     378        this._updateStatus();
     379    }
     380
     381    _handleTestGroupTestAdded(event)
     382    {
     383        let {test} = event.data;
     384
     385        this.appendChild(new WI.AuditTreeElement(test));
     386    }
     387
    318388    _handleManagerEditingChanged(event)
    319389    {
  • trunk/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.css

    r265118 r266317  
    202202    width: 1.5em;
    203203    min-width: 1.5em;
    204     margin: 0 var(--recording-auto-capture-input-margin);
     204    margin: 0 4px;
    205205    text-align: center;
    206 }
    207 
    208 .navigation-bar > .item.canvas-recording-auto-capture > label > input::-webkit-inner-spin-button {
    209     -webkit-appearance: none;
    210206}
    211207
  • trunk/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.js

    r255396 r266317  
    5050            this._recordingAutoCaptureFrameCountInputElement.type = "number";
    5151            this._recordingAutoCaptureFrameCountInputElement.min = 0;
    52             this._recordingAutoCaptureFrameCountInputElement.style.setProperty("--recording-auto-capture-input-margin", CanvasOverviewContentView.recordingAutoCaptureInputMargin + "px");
    5352            this._recordingAutoCaptureFrameCountInputElement.addEventListener("input", this._handleRecordingAutoCaptureInput.bind(this));
    5453            this._recordingAutoCaptureFrameCountInputElementValue = WI.settings.canvasRecordingAutoCaptureFrameCount.value;
     
    6968        this._savedRecordingsTreeOutline = null;
    7069    }
    71 
    72     // Static
    73 
    74     static get recordingAutoCaptureInputMargin() { return 4; }
    7570
    7671    // Public
     
    227222        }
    228223
    229         WI.ImageUtilities.scratchCanvasContext2D((context) => {
    230             if (!this._recordingAutoCaptureFrameCountInputElement.__cachedFont) {
    231                 let computedStyle = window.getComputedStyle(this._recordingAutoCaptureFrameCountInputElement);
    232                 this._recordingAutoCaptureFrameCountInputElement.__cachedFont = computedStyle.font;
    233             }
    234 
    235             context.font = this._recordingAutoCaptureFrameCountInputElement.__cachedFont;
    236             let textMetrics = context.measureText(this._recordingAutoCaptureFrameCountInputElement.value || this._recordingAutoCaptureFrameCountInputElement.placeholder);
    237             this._recordingAutoCaptureFrameCountInputElement.style.setProperty("width", (textMetrics.width + (2 * CanvasOverviewContentView.recordingAutoCaptureInputMargin)) + "px");
    238         });
     224        this._recordingAutoCaptureFrameCountInputElement.autosize();
    239225
    240226        return frameCount;
  • trunk/Source/WebInspectorUI/UserInterface/Views/Main.css

    r265027 r266317  
    231231    display: inline-flex;
    232232    height: 20px;
    233     padding: 0 4px;
    234233    border-bottom: none;
    235     vertical-align: -3px;
     234    vertical-align: var(--navigation-item-help-navigation-bar-vertical-align, sub);
    236235}
    237236
     
    250249.navigation-item-help > .navigation-bar > .item.button.text-only {
    251250    border: solid 1px var(--border-color);
     251}
     252
     253.message-text-view > .navigation-item-help + .navigation-item-help {
     254    margin-top: 4px;
    252255}
    253256
  • trunk/Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js

    r262806 r266317  
    7070        searchNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleDefaultContentViewSearchNavigationItemClicked, this);
    7171
    72         let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to see recent searches"), searchNavigationItem);
     72        let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to see recent searches."), searchNavigationItem);
    7373        contentPlaceholder.appendChild(importHelpElement);
    7474
  • trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css

    r265237 r266317  
    191191    --focus-ring-outline-offset: -2px;
    192192
     193    /* Invert colors yet preserve the hue */
     194    --filter-invert: invert(100%) hue-rotate(180deg);
     195
    193196    --undocked-title-area-height: 0px;
    194197    --tab-bar-height: var(--navigation-bar-height);
     
    363366        --timeline-scanner-color: hsl(0, 0%, 80%);
    364367
    365         /* Invert colors yet preserve the hue */
    366         --filter-invert: invert(100%) hue-rotate(180deg);
    367 
    368368        /* TODO: Use the same variable for the default theme */
    369369        --overlay-background: hsla(0, 0%, 24%, 0.9);
Note: See TracChangeset for help on using the changeset viewer.