WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
[patch]
Patch
bug-215555-20200816200246.patch (text/plain), 145.91 KB, created by
Devin Rousso
on 2020-08-16 20:02:47 PDT
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Devin Rousso
Created:
2020-08-16 20:02:47 PDT
Size:
145.91 KB
patch
obsolete
>diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog >index d553b9943bd2abbe7a4ad83789094581b30918c4..9f9f0fa8cecf5e30bfb4a610f6207607177edbfe 100644 >--- a/Source/WebInspectorUI/ChangeLog >+++ b/Source/WebInspectorUI/ChangeLog >@@ -1,3 +1,280 @@ >+2020-08-16 Devin Rousso <drousso@apple.com> >+ >+ Web Inspector: Audit: should be able to create/edit imported audits >+ https://bugs.webkit.org/show_bug.cgi?id=215555 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ 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.async runSetup): Added. >+ (WI.AuditTestBase.prototype.async clone): Added. >+ (WI.AuditTestBase.remove): Added. >+ (WI.AuditTestBase.toJSON): >+ (WI.AuditTestBase.prototype.updateResult): Added. >+ (WI.AuditTestBase.prototype.checkSupported): Added. >+ (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.updateResult): Added. >+ (WI.AuditTestGroup.prototype.checkSupported): Added. >+ (WI.AuditTestGroup.prototype._handleTestCompleted): >+ (WI.AuditTestGroup.prototype._handleTestDisabledChanged): >+ (WI.AuditTestGroup.prototype._handleTestRemove): Added. >+ (WI.AuditTestGroup.prototype._handleTestSupportedChanged): Added. >+ (WI.AuditTestGroup.prototype._handleTestChanged): Added. >+ (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.isTopLevel): Added. >+ (WI.AuditManager.prototype._handleTestRemove): Added. >+ (WI.AuditManager.prototype._addDefaultTests): >+ (WI.AuditManager.prototype._addDefaultTests.removeWhitespace): >+ (WI.AuditManager.prototype._addTest): 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._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. >+ (.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. >+ 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.prototype.createNameElement): Added. >+ (WI.AuditTestContentView.prototype.createDescriptionElement): Added. >+ (WI.AuditTestContentView.prototype.createControlsElement): Added. >+ (WI.AuditTestContentView.prototype.layout): >+ (WI.AuditTestContentView.prototype.shown): >+ (WI.AuditTestContentView.prototype.hidden): >+ (WI.AuditTestContentView.prototype.handleResultChanged): >+ (WI.AuditTestContentView.prototype.showNoResultPlaceholder): >+ (WI.AuditTestContentView.prototype._updateExportNavigationItems): Added. >+ (WI.AuditTestContentView.prototype._updateSupportsInputState): Added. >+ (WI.AuditTestContentView.prototype._createSetupEditor): Added. >+ (WI.AuditTestContentView.prototype._handleExportTestButtonNavigationItemClicked): Added. >+ (WI.AuditTestContentView.prototype._handleExportResultButtonNavigationItemClicked): Added. >+ (WI.AuditTestContentView.prototype._handleTestDisabledChanged): Added. >+ (WI.AuditTestContentView.prototype._handleEditingChanged): Added. >+ (WI.AuditTestContentView.prototype._updateExportButtonNavigationItemState): Deleted. >+ (WI.AuditTestContentView.prototype._handleExportButtonNavigationItemClicked): Deleted. >+ * UserInterface/Views/AuditTestContentView.css: >+ (.content-view.audit-test.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:not(.manager-editing) > header .description:empty): Added. >+ (.content-view.audit-test > header .controls): Added. >+ (.content-view.audit-test > header .controls .row + .row): Added. >+ (.content-view.audit-test > header .controls .setup label): Added. >+ (.content-view.audit-test > header .controls .setup .editor): Added. >+ (.content-view.audit-test:not(.manager-editing) > header .controls): Added. >+ (.content-view.audit-test > header .controls > .supports input[type="number"]): Added. >+ (.content-view.audit-test > header .controls > .supports .warning): Added. >+ (.content-view.audit-test > header .controls > .supports .warning:not(:empty)::before): 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 > section > .message-text-view > :is(progress, .indeterminate-progress-spinner)): 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.createControlsElement): 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.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 > header h1 > img): Added. >+ (.content-view.audit-test-case.manager-editing.disabled:not(.editable) > header h1 > img): Added. >+ (.content-view.audit-test-case.manager-editing.editable > header h1 > img:hover): Added. >+ (.content-view.audit-test-case.manager-editing.editable > header h1 > img:active): 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. >+ (.tree-outline .item.audit.unsupported > .status > img): >+ (@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): >+ New popover for creating an audit: >+ >+ [<select> of group or test case] [<input> for name] >+ >+ * UserInterface/Views/Main.css: >+ (.navigation-item-help:not(:first-child)): Added. >+ (.navigation-item-help > .navigation-bar): >+ 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.prototype.initialLayout): >+ 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/Base/Object.js: >+ (WI.Object.countEventListeners): Added. >+ (WI.Object.hasEventListeners): >+ (WI.Object.prototype.countEventListeners): Added. >+ Add test/engineering function for checking how many event listeners there are for an event. >+ >+ * UserInterface/Main.html: >+ * Localizations/en.lproj/localizedStrings.js: >+ > 2020-08-14 Devin Rousso <drousso@apple.com> > > Web Inspector: Assertion Failed: Appended a string with newlines. This breaks the source map. >diff --git a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js >index 30c77829d99b359852e6518a9f009c8f6c69f2bd..8a71ff0ca107a565d592d0001ec0de5b1260ed2a 100644 >--- a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js >+++ b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js >@@ -108,6 +108,7 @@ localizedStrings["Add New Class"] = "Add New Class"; > localizedStrings["Add New Probe Expression"] = "Add New Probe Expression"; > localizedStrings["Add New Watch Expression"] = "Add New Watch Expression"; > localizedStrings["Add Pattern"] = "Add Pattern"; >+localizedStrings["Add Test Case"] = "Add Test Case"; > localizedStrings["Add a Class"] = "Add a Class"; > localizedStrings["Add new breakpoint action after this action"] = "Add new breakpoint action after this action"; > localizedStrings["Add new rule"] = "Add new rule"; >@@ -353,10 +354,12 @@ localizedStrings["Copy as cURL"] = "Copy as cURL"; > localizedStrings["Could not capture screenshot"] = "Could not capture screenshot"; > localizedStrings["Could not fetch properties. Object may no longer exist."] = "Could not fetch properties. Object may no longer exist."; > localizedStrings["Count"] = "Count"; >+localizedStrings["Create"] = "Create"; > localizedStrings["Create %s Rule"] = "Create %s Rule"; > localizedStrings["Create Breakpoint"] = "Create Breakpoint"; > localizedStrings["Create Local Override"] = "Create Local Override"; > localizedStrings["Create Resource"] = "Create Resource"; >+localizedStrings["Create audit:"] = "Create audit:"; > localizedStrings["Cross-Origin Restrictions"] = "Cross-Origin Restrictions"; > localizedStrings["Current"] = "Current"; > localizedStrings["Current State"] = "Current State"; >@@ -390,13 +393,16 @@ localizedStrings["Deferred pause from blackboxed script"] = "Deferred pause from > /* Tooltip for a time range bar that represents when a CSS animation/transition is delayed */ > localizedStrings["Delay"] = "Delay"; > localizedStrings["Delete"] = "Delete"; >+localizedStrings["Delete Audit"] = "Delete Audit"; > localizedStrings["Delete Blackbox"] = "Delete Blackbox"; > localizedStrings["Delete Breakpoint"] = "Delete Breakpoint"; > localizedStrings["Delete Breakpoints"] = "Delete Breakpoints"; > localizedStrings["Delete Descendant Breakpoints"] = "Delete Descendant Breakpoints"; >+localizedStrings["Delete Group"] = "Delete Group"; > localizedStrings["Delete Inspector Bootstrap Script"] = "Delete Inspector Bootstrap Script"; > localizedStrings["Delete Local Override"] = "Delete Local Override"; > localizedStrings["Delete Watch Expression"] = "Delete Watch Expression"; >+localizedStrings["Delete audit"] = "Delete audit"; > localizedStrings["Delete probe"] = "Delete probe"; > localizedStrings["Delete this breakpoint action"] = "Delete this breakpoint action"; > localizedStrings["Demo Audit"] = "Demo Audit"; >@@ -440,6 +446,7 @@ localizedStrings["Download"] = "Download"; > localizedStrings["Download Web Archive"] = "Download Web Archive"; > localizedStrings["Dropped Element"] = "Dropped Element"; > localizedStrings["Dropped Node"] = "Dropped Node"; >+localizedStrings["Duplicate Audit"] = "Duplicate Audit"; > localizedStrings["Duplicate Selector"] = "Duplicate Selector"; > localizedStrings["Duplicate property"] = "Duplicate property"; > localizedStrings["Duration"] = "Duration"; >@@ -550,12 +557,12 @@ localizedStrings["Expanded"] = "Expanded"; > localizedStrings["Experimental"] = "Experimental"; > localizedStrings["Export"] = "Export"; > localizedStrings["Export (%s)"] = "Export (%s)"; >+localizedStrings["Export Audit"] = "Export Audit"; > localizedStrings["Export HAR"] = "Export HAR"; > localizedStrings["Export Result"] = "Export Result"; >-localizedStrings["Export Test"] = "Export Test"; >+localizedStrings["Export Result (%s)"] = "Export Result (%s)"; > localizedStrings["Export recording (%s)"] = "Export recording (%s)"; > localizedStrings["Export recording (%s)\nShift-click to export a HTML reduction"] = "Export recording (%s)\nShift-click to export a HTML reduction"; >-localizedStrings["Export result (%s)"] = "Export result (%s)"; > localizedStrings["Expression"] = "Expression"; > localizedStrings["Extension Scripts"] = "Extension Scripts"; > localizedStrings["Extension Style Sheets"] = "Extension Style Sheets"; >@@ -563,6 +570,7 @@ localizedStrings["Extensions"] = "Extensions"; > localizedStrings["Extra Scripts"] = "Extra Scripts"; > localizedStrings["Extra Style Sheets"] = "Extra Style Sheets"; > localizedStrings["Fade unexecuted code"] = "Fade unexecuted code"; >+localizedStrings["Fail"] = "Fail"; > localizedStrings["Failed to upgrade"] = "Failed to upgrade"; > localizedStrings["Failure status code"] = "Failure status code"; > /* Resource loaded via 'fetch' method */ >@@ -680,9 +688,10 @@ localizedStrings["Images"] = "Images"; > localizedStrings["Images:"] = "Images:"; > localizedStrings["Immediate Pause Requested"] = "Immediate Pause Requested"; > localizedStrings["Import"] = "Import"; >-localizedStrings["Import Audit"] = "Import Audit"; >+localizedStrings["Import Audit or Result"] = "Import Audit or Result"; > localizedStrings["Import HAR"] = "Import HAR"; > localizedStrings["Import Recording"] = "Import Recording"; >+localizedStrings["Import audit or result"] = "Import audit or result"; > localizedStrings["Imported"] = "Imported"; > localizedStrings["Imported - %s"] = "Imported - %s"; > localizedStrings["Imported \u2014 %s"] = "Imported \u2014 %s"; >@@ -862,6 +871,7 @@ localizedStrings["Node Removed"] = "Node Removed"; > localizedStrings["Nodes"] = "Nodes"; > localizedStrings["None"] = "None"; > localizedStrings["Not found"] = "Not found"; >+localizedStrings["Not yet run"] = "Not yet run"; > localizedStrings["Object Graph"] = "Object Graph"; > localizedStrings["Object Store"] = "Object Store"; > localizedStrings["Observer Callback"] = "Observer Callback"; >@@ -908,6 +918,7 @@ localizedStrings["Paints"] = "Paints"; > localizedStrings["Paints @ Column title"] = "Paints"; > localizedStrings["Parent"] = "Parent"; > localizedStrings["Partial Garbage Collection"] = "Partial Garbage Collection"; >+localizedStrings["Pass"] = "Pass"; > localizedStrings["Passive"] = "Passive"; > localizedStrings["Path"] = "Path"; > localizedStrings["Pause Processing"] = "Pause Processing"; >@@ -931,11 +942,15 @@ localizedStrings["Power Efficient Playback"] = "Power Efficient Playback"; > localizedStrings["Prefer Shorthands"] = "Prefer Shorthands"; > localizedStrings["Prefer indent using:"] = "Prefer indent using:"; > localizedStrings["Preserve Log"] = "Preserve Log"; >-localizedStrings["Press %s to import a test or result file"] = "Press %s to import a test or result file"; >+localizedStrings["Press %s to create a new audit."] = "Press %s to create a new audit."; >+localizedStrings["Press %s to enable audits."] = "Press %s to enable audits."; >+localizedStrings["Press %s to import an audit or a result."] = "Press %s to import an audit or a result."; > localizedStrings["Press %s to load a recording from file."] = "Press %s to load a recording from file."; >-localizedStrings["Press %s to see recent searches"] = "Press %s to see recent searches"; >-localizedStrings["Press %s to start running the audit"] = "Press %s to start running the audit"; >-localizedStrings["Press %s to stop editing"] = "Press %s to stop editing"; >+localizedStrings["Press %s to see recent searches."] = "Press %s to see recent searches."; >+localizedStrings["Press %s to start editing audits."] = "Press %s to start editing audits."; >+localizedStrings["Press %s to start running the audit."] = "Press %s to start running the audit."; >+localizedStrings["Press %s to stop editing audits."] = "Press %s to stop editing audits."; >+localizedStrings["Press %s to stop running."] = "Press %s to stop running."; > localizedStrings["Pressed"] = "Pressed"; > localizedStrings["Pretty print"] = "Pretty print"; > localizedStrings["Preview"] = "Preview"; >@@ -1091,6 +1106,8 @@ localizedStrings["Secure"] = "Secure"; > localizedStrings["Security"] = "Security"; > localizedStrings["Security Issue"] = "Security Issue"; > localizedStrings["Security Origin"] = "Security Origin"; >+localizedStrings["Select an audit in the navigation sidebar to edit it."] = "Select an audit in the navigation sidebar to edit it."; >+localizedStrings["Select an audit in the navigation sidebar to view its results."] = "Select an audit in the navigation sidebar to view its results."; > localizedStrings["Select baseline snapshot"] = "Select baseline snapshot"; > localizedStrings["Select comparison snapshot"] = "Select comparison snapshot"; > localizedStrings["Selected"] = "Selected"; >@@ -1122,6 +1139,7 @@ localizedStrings["Setter"] = "Setter"; > localizedStrings["Settings Tab Name"] = "Settings"; > /* Title of Settings Tab with keyboard shortcut */ > localizedStrings["Settings Tab Title"] = "Settings (%s)"; >+localizedStrings["Setup: "] = "Setup: "; > localizedStrings["Shader Programs"] = "Shader Programs"; > localizedStrings["Shadow Content"] = "Shadow Content"; > localizedStrings["Shadow Content (%s)"] = "Shadow Content (%s)"; >@@ -1184,6 +1202,7 @@ localizedStrings["Spread @ Box Shadow Editor"] = "Spread"; > localizedStrings["Staging:"] = "Staging:"; > localizedStrings["Stalled"] = "Stalled"; > localizedStrings["Start"] = "Start"; >+localizedStrings["Start Audit"] = "Start Audit"; > localizedStrings["Start Time"] = "Start Time"; > localizedStrings["Start element selection (%s)"] = "Start element selection (%s)"; > localizedStrings["Start recording (%s)\nCreate new recording (%s)"] = "Start recording (%s)\nCreate new recording (%s)"; >@@ -1225,6 +1244,7 @@ localizedStrings["Subject"] = "Subject"; > /* A submenu item of 'Break On' that breaks (pauses) before child DOM node is modified */ > localizedStrings["Subtree Modified"] = "Subtree Modified"; > localizedStrings["Summary"] = "Summary"; >+localizedStrings["Supports: "] = "Supports: "; > /* Label of dropdown item used for forcing Web Inspector to be shown using the system's theme */ > localizedStrings["System @ Settings General Appearance"] = "System"; > localizedStrings["TCP"] = "TCP"; >@@ -1236,6 +1256,7 @@ localizedStrings["Tag"] = "Tag"; > localizedStrings["Take snapshot"] = "Take snapshot"; > localizedStrings["Target"] = "Target"; > localizedStrings["Template Content"] = "Template Content"; >+localizedStrings["Test Case"] = "Test Case"; > localizedStrings["Text"] = "Text"; > localizedStrings["Text Frame"] = "Text Frame"; > localizedStrings["Text Node"] = "Text Node"; >@@ -1279,6 +1300,7 @@ localizedStrings["This resource was loaded from a local override"] = "This resou > localizedStrings["This resource was loaded from a service worker"] = "This resource was loaded from a service worker"; > localizedStrings["This resource was loaded from the disk cache"] = "This resource was loaded from the disk cache"; > localizedStrings["This resource was loaded from the memory cache"] = "This resource was loaded from the memory cache"; >+localizedStrings["This test should not run because it should be unsupported."] = "This test should not run because it should be unsupported."; > localizedStrings["This test will pass with a variety of accessibility information about the <body> element."] = "This test will pass with a variety of accessibility information about the <body> element."; > localizedStrings["This test will pass with all DOM nodes that have a computed role of \u0022link\u0022."] = "This test will pass with all DOM nodes that have a computed role of \u0022link\u0022."; > localizedStrings["This test will pass with all child nodes that are selected (\u0022%s\u0022) of the <body> element in the accessibility tree."] = "This test will pass with all child nodes that are selected (\u0022%s\u0022) of the <body> element in the accessibility tree."; >@@ -1348,6 +1370,7 @@ localizedStrings["Unique"] = "Unique"; > localizedStrings["Unknown Location"] = "Unknown Location"; > localizedStrings["Unknown error"] = "Unknown error"; > localizedStrings["Unknown node"] = "Unknown node"; >+localizedStrings["Unsupported"] = "Unsupported"; > localizedStrings["Unsupported property name"] = "Unsupported property name"; > localizedStrings["Unsupported property value"] = "Unsupported property value"; > localizedStrings["Untitled"] = "Untitled"; >@@ -1384,6 +1407,7 @@ localizedStrings["Waiting for animations created by JavaScript."] = "Waiting for > localizedStrings["Waiting for canvas contexts created by script or CSS."] = "Waiting for canvas contexts created by script or CSS."; > localizedStrings["Waiting for frames\u2026"] = "Waiting for frames\u2026"; > localizedStrings["Waiting for transitions created by CSS."] = "Waiting for transitions created by CSS."; >+localizedStrings["Warn"] = "Warn"; > localizedStrings["Warning: "] = "Warning: "; > localizedStrings["Warnings"] = "Warnings"; > localizedStrings["Watch Expressions"] = "Watch Expressions"; >@@ -1470,8 +1494,8 @@ localizedStrings["\u0022%s\u0022 has a non-string \u0022%s\u0022 value"] = "\u00 > localizedStrings["\u0022%s\u0022 has an invalid \u0022%s\u0022 value"] = "\u0022%s\u0022 has an invalid \u0022%s\u0022 value"; > localizedStrings["\u0022%s\u0022 is not JSON serializable"] = "\u0022%s\u0022 is not JSON serializable"; > localizedStrings["\u0022%s\u0022 is not valid for %s"] = "\u0022%s\u0022 is not valid for %s"; >+localizedStrings["\u0022%s\u0022 is too new to run in the inspected page"] = "\u0022%s\u0022 is too new to run in the inspected page"; > localizedStrings["\u0022%s\u0022 is too new to run in this Web Inspector"] = "\u0022%s\u0022 is too new to run in this Web Inspector"; >-localizedStrings["\u0022%s\u0022 is too new to run on this inspected page"] = "\u0022%s\u0022 is too new to run on this inspected page"; > localizedStrings["\u0022%s\u0022 must be a %s"] = "\u0022%s\u0022 must be a %s"; > localizedStrings["\u0022%s\u0022 must be an %s"] = "\u0022%s\u0022 must be an %s"; > localizedStrings["\u0022%s\u0022 threw an error"] = "\u0022%s\u0022 threw an error"; >@@ -1503,6 +1527,8 @@ localizedStrings["spaces"] = "spaces"; > localizedStrings["time before stopping"] = "time before stopping"; > localizedStrings["times before stopping"] = "times before stopping"; > localizedStrings["toggle"] = "toggle"; >+localizedStrings["too new to run in the inspected page"] = "too new to run in the inspected page"; >+localizedStrings["too new to run in this Web Inspector"] = "too new to run in this Web Inspector"; > localizedStrings["unknown %s \u0022%s\u0022"] = "unknown %s \u0022%s\u0022"; > localizedStrings["unsupported %s"] = "unsupported %s"; > localizedStrings["unsupported HAR version"] = "unsupported HAR version"; >diff --git a/Source/WebInspectorUI/UserInterface/Base/Object.js b/Source/WebInspectorUI/UserInterface/Base/Object.js >index 400431309736976da19ec04bcb1dc1c1513074ae..3549fb9fa1e94df9d79ed0332a9bd23c703251e8 100644 >--- a/Source/WebInspectorUI/UserInterface/Base/Object.js >+++ b/Source/WebInspectorUI/UserInterface/Base/Object.js >@@ -100,14 +100,18 @@ WI.Object = class WebInspectorObject > return wrapper.promise; > } > >+ static countEventListeners(eventType) >+ { >+ console.assert(WI.isEngineeringBuild); >+ >+ let listenersTable = this._listeners?.get(eventType); >+ return listenersTable?.size || 0; >+ } >+ > // Only used by tests. > static hasEventListeners(eventType) > { >- if (!this._listeners) >- return false; >- >- let listenersTable = this._listeners.get(eventType); >- return listenersTable && listenersTable.size > 0; >+ return this.countEventListeners(eventType) > 0; > } > > // This should only be used within regression tests to detect leaks. >@@ -134,6 +138,7 @@ WI.Object = class WebInspectorObject > singleFireEventListener() { return WI.Object.singleFireEventListener.apply(this, arguments); } > removeEventListener() { return WI.Object.removeEventListener.apply(this, arguments); } > awaitEvent() { return WI.Object.awaitEvent.apply(this, arguments); } >+ countEventListeners() { return WI.Object.countEventListeners.apply(this, arguments); } > hasEventListeners() { return WI.Object.hasEventListeners.apply(this, arguments); } > retainedObjectsWithPrototype() { return WI.Object.retainedObjectsWithPrototype.apply(this, arguments); } > >diff --git a/Source/WebInspectorUI/UserInterface/Base/Utilities.js b/Source/WebInspectorUI/UserInterface/Base/Utilities.js >index 7e2bbdd35e60b3a8baf7db84ea81efb1d46b46b5..e98bc0435383cfdbd4d7d6a7ef3333c3eed332d9 100644 >--- a/Source/WebInspectorUI/UserInterface/Base/Utilities.js >+++ b/Source/WebInspectorUI/UserInterface/Base/Utilities.js >@@ -484,6 +484,29 @@ Object.defineProperty(DocumentFragment.prototype, "createChild", > value: Element.prototype.createChild > }); > >+(function() { >+ const fontSymbol = Symbol("font"); >+ >+ Object.defineProperty(HTMLInputElement.prototype, "autosize", >+ { >+ value(extra = 0) >+ { >+ extra += 6; // UserAgent styles add 1px padding and 2px border. >+ if (this.type === "number") >+ extra += 13; // Number input inner spin button width. >+ extra += 2; // Add extra pixels for the cursor. >+ >+ WI.ImageUtilities.scratchCanvasContext2D((context) => { >+ this[fontSymbol] ||= window.getComputedStyle(this).font; >+ >+ context.font = this[fontSymbol]; >+ let textMetrics = context.measureText(this.value || this.placeholder); >+ this.style.setProperty("width", (textMetrics.width + extra) + "px"); >+ }); >+ }, >+ }); >+})(); >+ > Object.defineProperty(Event.prototype, "stop", > { > value() >diff --git a/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js b/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js >index c375c6ed6db0da357a0469f7eed39b835e0ddcfb..999c60bf95828be6014c647c60308ccf6260a6c3 100644 >--- a/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js >+++ b/Source/WebInspectorUI/UserInterface/Controllers/AuditManager.js >@@ -103,7 +103,7 @@ WI.AuditManager = class AuditManager extends WI.Object > > let disabledDefaultTests = []; > let saveDisabledDefaultTest = (test) => { >- if (test.disabled) >+ if (test.supported && test.disabled) > disabledDefaultTests.push(test.name); > > if (test instanceof WI.AuditTestGroup) { >@@ -113,7 +113,7 @@ WI.AuditManager = class AuditManager extends WI.Object > }; > > for (let test of this._tests) { >- if (test.__default) >+ if (test[WI.AuditTestBase.DefaultAuditSymbol]) > saveDisabledDefaultTest(test); > else > WI.objectStores.audits.putObject(test); >@@ -141,6 +141,8 @@ WI.AuditManager = class AuditManager extends WI.Object > let mainResource = WI.networkManager.mainFrame.mainResource; > > this._runningState = WI.AuditManager.RunningState.Active; >+ this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged); >+ > this._runningTests = tests; > for (let test of this._runningTests) > test.clearResult(); >@@ -159,7 +161,7 @@ WI.AuditManager = class AuditManager extends WI.Object > let topLevelTest = this._topLevelTestForTest(test); > console.assert(topLevelTest || window.InspectorTest, "No matching top-level test found", test); > if (topLevelTest) >- await topLevelTest.setup(); >+ await topLevelTest.runSetup(); > > await test.start(); > >@@ -170,6 +172,8 @@ WI.AuditManager = class AuditManager extends WI.Object > let result = this._runningTests.map((test) => test.result).filter((result) => !!result); > > this._runningState = WI.AuditManager.RunningState.Inactive; >+ this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged); >+ > this._runningTests = []; > > this._addResult(result); >@@ -190,6 +194,7 @@ WI.AuditManager = class AuditManager extends WI.Object > return; > > this._runningState = WI.AuditManager.RunningState.Stopping; >+ this.dispatchEventToListeners(WI.AuditManager.Event.RunningStateChanged); > > for (let test of this._runningTests) > test.stop(); >@@ -218,7 +223,7 @@ WI.AuditManager = class AuditManager extends WI.Object > return; > > if (object instanceof WI.AuditTestBase) { >- this._addTest(object); >+ this.addTest(object); > WI.objectStores.audits.putObject(object); > } else if (object instanceof WI.AuditTestResultBase) > this._addResult(object); >@@ -257,14 +262,30 @@ WI.AuditManager = class AuditManager extends WI.Object > const key = null; > WI.objectStores.audits.associateObject(test, key, payload); > >- this._addTest(test); >+ this.addTest(test); > } > }); > } > >+ addTest(test) >+ { >+ console.assert(test instanceof WI.AuditTestBase, test); >+ console.assert(!this._tests.includes(test), test); >+ >+ this._tests.push(test); >+ >+ test.addEventListener(WI.AuditTestBase.Event.Remove, this._handleTestRemove, this); >+ >+ this.dispatchEventToListeners(WI.AuditManager.Event.TestAdded, {test}); >+ } >+ > removeTest(test) > { >- if (test.__default) { >+ console.assert(this.editing); >+ console.assert(test instanceof WI.AuditTestBase, test); >+ console.assert(this._tests.includes(test) || test[WI.AuditTestBase.DefaultAuditSymbol], test); >+ >+ if (test[WI.AuditTestBase.DefaultAuditSymbol]) { > if (test.disabled) { > InspectorFrontendHost.beep(); > return; >@@ -279,22 +300,20 @@ WI.AuditManager = class AuditManager extends WI.Object > return; > } > >+ console.assert(test.editable, test); >+ > this._tests.remove(test); > > this.dispatchEventToListeners(WI.AuditManager.Event.TestRemoved, {test}); >+ } > >- WI.objectStores.audits.deleteObject(test); >+ isTopLevel(test) >+ { >+ return this._tests.includes(test); > } > > // Private > >- _addTest(test) >- { >- this._tests.push(test); >- >- this.dispatchEventToListeners(WI.AuditManager.Event.TestAdded, {test}); >- } >- > _addResult(result) > { > if (!result || (Array.isArray(result) && !result.length)) >@@ -343,6 +362,11 @@ WI.AuditManager = class AuditManager extends WI.Object > } > } > >+ _handleTestRemove(event) >+ { >+ this.removeTest(event.target); >+ } >+ > _addDefaultTests() > { > const levelPass = function() { >@@ -460,6 +484,10 @@ WI.AuditManager = class AuditManager extends WI.Object > return {level: "pass", mainResource, resourceContent: WebInspectorAudit.Resources.getResourceContent(mainResource.id)}; > }; > >+ const unsupported = function() { >+ throw Error("this test should not be supported."); >+ }; >+ > const testMenuRoleForRequiredChildren = function() { > const relationships = { > menu: ["menuitem", "menuitemcheckbox", "menuitemradio"], >@@ -998,7 +1026,7 @@ WI.AuditManager = class AuditManager extends WI.Object > }; > > function removeWhitespace(func) { >- return func.toString().replace(/\s+/g, " "); >+ return WI.AuditTestCase.stringifyFunction(func, 8); > } > > const defaultTests = [ >@@ -1038,6 +1066,7 @@ WI.AuditManager = class AuditManager extends WI.Object > new WI.AuditTestCase("getResourceContent", removeWhitespace(getResourceContent), {description: WI.UIString("This test will pass with the contents of the main resource."), supports: 3}), > ], {description: WI.UIString("These tests demonstrate how to use %s to get information about loaded resources.").format(WI.unlocalizedString("WebInspectorAudit.Resources")), supports: 2}), > ], {description: WI.UIString("These tests demonstrate how to use %s to access information not normally available to JavaScript.").format(WI.unlocalizedString("WebInspectorAudit")), supports: 1}), >+ new WI.AuditTestCase("unsupported", removeWhitespace(unsupported), {description: WI.UIString("This test should not run because it should be unsupported."), supports: Infinity}), > ], {description: WI.UIString("These tests serve as a demonstration of the functionality and structure of audits.")}), > new WI.AuditTestGroup(WI.UIString("Accessibility"), [ > new WI.AuditTestCase("testMenuRoleForRequiredChildren", removeWhitespace(testMenuRoleForRequiredChildren), {description: WI.UIString("Ensure that element of role \u0022%s\u0022 and \u0022%s\u0022 have required owned elements in accordance with WAI-ARIA.").format(WI.unlocalizedString("menu"), WI.unlocalizedString("menubar")), supports: 1}), >@@ -1066,6 +1095,8 @@ WI.AuditManager = class AuditManager extends WI.Object > ]; > > let checkDisabledDefaultTest = (test) => { >+ test[WI.AuditTestBase.DefaultAuditSymbol] = true; >+ > if (this._disabledDefaultTestsSetting.value.includes(test.name)) > test.disabled = true; > >@@ -1078,8 +1109,7 @@ WI.AuditManager = class AuditManager extends WI.Object > for (let test of defaultTests) { > checkDisabledDefaultTest(test); > >- test.__default = true; >- this._addTest(test); >+ this.addTest(test); > } > } > }; >@@ -1093,6 +1123,7 @@ WI.AuditManager.RunningState = { > > WI.AuditManager.Event = { > EditingChanged: "audit-manager-editing-changed", >+ RunningStateChanged: "audit-manager-running-state-changed", > TestAdded: "audit-manager-test-added", > TestCompleted: "audit-manager-test-completed", > TestRemoved: "audit-manager-test-removed", >diff --git a/Source/WebInspectorUI/UserInterface/Main.html b/Source/WebInspectorUI/UserInterface/Main.html >index 5db2715ebc2284c96b1cb6f9f634e5b4287f6c74..db468d67f16f5bd16a5c7a9cc1c3b2e426efe13f 100644 >--- a/Source/WebInspectorUI/UserInterface/Main.html >+++ b/Source/WebInspectorUI/UserInterface/Main.html >@@ -82,6 +82,7 @@ > <link rel="stylesheet" href="Views/ContentViewContainer.css"> > <link rel="stylesheet" href="Views/CookiePopover.css"> > <link rel="stylesheet" href="Views/CookieStorageContentView.css"> >+ <link rel="stylesheet" href="Views/CreateAuditPopover.css"> > <link rel="stylesheet" href="Views/DOMBreakpointTreeElement.css"> > <link rel="stylesheet" href="Views/DOMEventsBreakdownView.css"> > <link rel="stylesheet" href="Views/DOMNodeDetailsSidebarPanel.css"> >@@ -649,6 +650,7 @@ > <script src="Views/CookiePopover.js"></script> > <script src="Views/CookieStorageContentView.js"></script> > <script src="Views/CookieStorageTreeElement.js"></script> >+ <script src="Views/CreateAuditPopover.js"></script> > <script src="Views/DOMBreakpointTreeElement.js"></script> > <script src="Views/DOMEventsBreakdownView.js"></script> > <script src="Views/DOMNodeDetailsSidebarPanel.js"></script> >diff --git a/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js b/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js >index 2829ec5663f432758f386cda36dabe1c6b8814f8..85c5ca6aae8475a81f7fec11dff8cd40d6572196 100644 >--- a/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js >+++ b/Source/WebInspectorUI/UserInterface/Models/AuditTestBase.js >@@ -27,34 +27,24 @@ WI.AuditTestBase = class AuditTestBase extends WI.Object > { > constructor(name, {description, supports, setup, disabled} = {}) > { >- console.assert(typeof name === "string"); >- console.assert(!description || typeof description === "string"); >- console.assert(supports === undefined || typeof supports === "number"); >- console.assert(!setup || typeof setup === "string"); >- console.assert(disabled === undefined || typeof disabled === "boolean"); >+ console.assert(typeof name === "string", name); >+ console.assert(!description || typeof description === "string", description); >+ console.assert(supports === undefined || typeof supports === "number", supports); >+ console.assert(!setup || typeof setup === "string", setup); >+ console.assert(disabled === undefined || typeof disabled === "boolean", disabled); > > super(); > > // This class should not be instantiated directly. Create a concrete subclass instead. >- console.assert(this.constructor !== WI.AuditTestBase && this instanceof WI.AuditTestBase); >+ console.assert(this.constructor !== WI.AuditTestBase && this instanceof WI.AuditTestBase, this); > > this._name = name; >- this._description = description || null; >- this._supports = supports; >- this._setup = setup || null; >+ this._description = description || ""; >+ this._supports = supports ?? NaN; >+ this._setup = setup || ""; > > this._supported = true; >- if (typeof this._supports === "number") { >- if (this._supports > WI.AuditTestBase.Version) { >- WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run in this Web Inspector").format(this.name)); >- this._supported = false; >- } else if (this._supports > InspectorBackend.getVersion("Audit")) { >- WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run on this inspected page").format(this.name)); >- this._supported = false; >- } >- } >- >- if (!this.supported) >+ if (!isNaN(this._supports) && !this.checkSupported({warn: true, preventDisable: true})) > disabled = true; > > this._runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive; >@@ -63,11 +53,66 @@ WI.AuditTestBase = class AuditTestBase extends WI.Object > > // Public > >- get name() { return this._name; } >- get description() { return this._description; } > get runningState() { return this._runningState; } > get result() { return this._result; } > >+ get name() >+ { >+ return this._name; >+ } >+ >+ set name(name) >+ { >+ console.assert(this.editable); >+ console.assert(WI.auditManager.editing); >+ console.assert(typeof name === "string", name); >+ >+ if (name === this._name) >+ return; >+ >+ let oldName = this._name; >+ >+ this._name = name; >+ >+ this.dispatchEventToListeners(WI.AuditTestBase.Event.NameChanged, {oldName}); >+ } >+ >+ get description() >+ { >+ return this._description; >+ } >+ >+ set description(description) >+ { >+ console.assert(this.editable); >+ console.assert(WI.auditManager.editing); >+ console.assert(typeof description === "string", description); >+ >+ if (description === this._description) >+ return; >+ >+ this._description = description; >+ } >+ >+ get supports() >+ { >+ return this._supports; >+ } >+ >+ set supports(supports) >+ { >+ console.assert(this.editable); >+ console.assert(WI.auditManager.editing); >+ console.assert(typeof supports === "number", supports); >+ >+ if (supports === this._supports) >+ return; >+ >+ this._supports = supports; >+ >+ this.checkSupported(); >+ } >+ > get supported() > { > return this._supported; >@@ -80,6 +125,25 @@ WI.AuditTestBase = class AuditTestBase extends WI.Object > this.disabled = true; > } > >+ get setup() >+ { >+ return this._setup; >+ } >+ >+ set setup(setup) >+ { >+ console.assert(this.editable); >+ console.assert(WI.auditManager.editing); >+ console.assert(typeof setup === "string", setup); >+ >+ if (setup === this._setup) >+ return; >+ >+ this._setup = setup; >+ >+ this.clearResult(); >+ } >+ > get disabled() > { > return this._runningState === WI.AuditManager.RunningState.Disabled; >@@ -95,16 +159,28 @@ WI.AuditTestBase = class AuditTestBase extends WI.Object > disabled = true; > > let runningState = disabled ? WI.AuditManager.RunningState.Disabled : WI.AuditManager.RunningState.Inactive; >- if (runningState === this._runningState) >+ if (runningState === this._runningState) { >+ console.assert(!this._result || !this.disabled); > return; >+ } > > this._runningState = runningState; > > this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged); >+ >+ if (this.disabled) >+ this.clearResult(); > } > >- async setup() >+ get editable() > { >+ return !this[WI.AuditTestBase.DefaultAuditSymbol]; >+ } >+ >+ async runSetup() >+ { >+ console.assert(WI.auditManager.isTopLevel(this)); >+ > if (!this._setup) > return; > >@@ -199,6 +275,23 @@ WI.AuditTestBase = class AuditTestBase extends WI.Object > return true; > } > >+ async clone() >+ { >+ console.assert(WI.auditManager.editing); >+ >+ return this.constructor.fromPayload(this.toJSON()); >+ } >+ >+ remove() >+ { >+ console.assert(WI.auditManager.editing); >+ >+ this.clearResult(); >+ >+ console.assert(this.countEventListeners(WI.AuditTestBase.Event.Remove) === 1); >+ this.dispatchEventToListeners(WI.AuditTestBase.Event.Remove); >+ } >+ > saveIdentityToCookie(cookie) > { > cookie["audit-" + this.constructor.TypeIdentifier + "-name"] = this._name; >@@ -212,8 +305,8 @@ WI.AuditTestBase = class AuditTestBase extends WI.Object > }; > if (this._description) > json.description = this._description; >- if (typeof this._supports === "number") >- json.supports = this._supports; >+ if (!isNaN(this._supports)) >+ json.supports = Number.isFinite(this._supports) ? this._supports : WI.AuditTestBase.Version + 1; > if (this._setup) > json.setup = this._setup; > if (key === WI.ObjectStore.toJSONSymbol) >@@ -227,6 +320,39 @@ WI.AuditTestBase = class AuditTestBase extends WI.Object > { > throw WI.NotImplementedError.subclassMustOverride(); > } >+ >+ updateResult(result) >+ { >+ console.assert(result instanceof WI.AuditTestResultBase, result); >+ >+ this._result = result; >+ >+ this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged); >+ } >+ >+ checkSupported({warn, preventDisable} = {}) >+ { >+ let supportedBefore = this._supported; >+ >+ if (this._supports > WI.AuditTestBase.Version) { >+ this._supported = false; >+ if (warn && supportedBefore !== this._supported && Number.isFinite(this._supports)) >+ WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run in this Web Inspector").format(this.name)); >+ } else if (InspectorBackend.hasDomain("Audit") && this._supports > InspectorBackend.getVersion("Audit")) { >+ this._supported = false; >+ if (warn && supportedBefore !== this._supported && Number.isFinite(this._supports)) >+ WI.AuditManager.synthesizeWarning(WI.UIString("\u0022%s\u0022 is too new to run in the inspected page").format(this.name)); >+ } else >+ this._supported = true; >+ >+ if (supportedBefore !== this._supported) >+ this.dispatchEventToListeners(WI.AuditTestBase.Event.SupportedChanged); >+ >+ if (!preventDisable && !this._supported) >+ this.disabled = true; >+ >+ return this._supported; >+ } > }; > > // Keep this in sync with Inspector::Protocol::Audit::VERSION. >@@ -234,11 +360,17 @@ WI.AuditTestBase.Version = 3; > > WI.AuditTestBase.ObjectGroup = "audit"; > >+WI.AuditTestBase.DefaultAuditSymbol = Symbol("default-audit"); >+ > WI.AuditTestBase.Event = { > Completed: "audit-test-base-completed", > DisabledChanged: "audit-test-base-disabled-changed", >+ NameChanged: "audit-test-base-name-changed", > Progress: "audit-test-base-progress", >+ Remove: "audit-test-base-remove", > ResultChanged: "audit-test-base-result-changed", > Scheduled: "audit-test-base-scheduled", > Stopping: "audit-test-base-stopping", >+ SupportedChanged: "audit-test-base-supported-changed", >+ TestChanged: "audit-test-base-test-changed", > }; >diff --git a/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js b/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js >index d967c3157b7dfb90de4ab94d77763eb33190ccb5..73b8c658ca25de7bbfde4be3b6081256ba632b50 100644 >--- a/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js >+++ b/Source/WebInspectorUI/UserInterface/Models/AuditTestCase.js >@@ -27,7 +27,7 @@ WI.AuditTestCase = class AuditTestCase extends WI.AuditTestBase > { > constructor(name, test, options = {}) > { >- console.assert(typeof test === "string"); >+ console.assert(typeof test === "string", test); > > super(name, options); > >@@ -77,9 +77,41 @@ WI.AuditTestCase = class AuditTestCase extends WI.AuditTestBase > return new WI.AuditTestCase(payload.name, payload.test, options); > } > >+ static stringifyFunction(func, indentLevel) >+ { >+ let string = func.toString(); >+ >+ // Remove spaces to make the function look unindented. >+ string = string.replaceAll(new RegExp(`^ {${indentLevel}}`, "gm"), ""); >+ >+ // Replace remaining indentations with the user set indent string. >+ string = string.replaceAll(/^ /gm, WI.indentString()); >+ >+ return string; >+ } >+ > // Public > >- get test() { return this._test; } >+ get test() >+ { >+ return this._test; >+ } >+ >+ set test(test) >+ { >+ console.assert(this.editable); >+ console.assert(WI.auditManager.editing); >+ console.assert(typeof test === "string", test); >+ >+ if (test === this._test) >+ return; >+ >+ this._test = test; >+ >+ this.clearResult(); >+ >+ this.dispatchEventToListeners(WI.AuditTestBase.Event.TestChanged); >+ } > > toJSON(key) > { >@@ -343,9 +375,7 @@ WI.AuditTestCase = class AuditTestCase extends WI.AuditTestBase > options.data = data; > if (resolvedDOMNodes) > options.resolvedDOMNodes = resolvedDOMNodes; >- this._result = new WI.AuditTestCaseResult(this.name, level, options); >- >- this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged); >+ this.updateResult(new WI.AuditTestCaseResult(this.name, level, options)); > } > }; > >diff --git a/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js b/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js >index 14c7834ef8d18c44ba4cea979a75c53d95d9180b..573a590fa6543303b056b179f06b7d09ac505c16 100644 >--- a/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js >+++ b/Source/WebInspectorUI/UserInterface/Models/AuditTestGroup.js >@@ -27,7 +27,7 @@ WI.AuditTestGroup = class AuditTestGroup extends WI.AuditTestBase > { > constructor(name, tests, options = {}) > { >- console.assert(Array.isArray(tests)); >+ console.assert(Array.isArray(tests), tests); > > // Set disabled once `_tests` is set so that it propagates. > let disabled = options.disabled; >@@ -35,28 +35,15 @@ WI.AuditTestGroup = class AuditTestGroup extends WI.AuditTestBase > > super(name, options); > >- this._tests = tests; > this._preventDisabledPropagation = false; >+ this._preventSupportedPropagation = false; > >- if (disabled || !this.supported) >+ this._tests = []; >+ for (let test of tests) >+ this.addTest(test); >+ >+ if (disabled) > this.disabled = true; >- >- let hasSupportedTest = false; >- >- for (let test of this._tests) { >- if (!this.supported) >- test.supported = false; >- else if (test.supported) >- hasSupportedTest = true; >- >- test.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this); >- test.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this); >- test.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this); >- >- } >- >- if (!hasSupportedTest) >- this.supported = false; > } > > // Static >@@ -121,6 +108,54 @@ WI.AuditTestGroup = class AuditTestGroup extends WI.AuditTestBase > > get tests() { return this._tests; } > >+ addTest(test) >+ { >+ console.assert(test instanceof WI.AuditTestBase, test); >+ console.assert(!this._tests.includes(test), test); >+ >+ this._tests.push(test); >+ >+ test.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this); >+ test.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this); >+ test.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this); >+ test.addEventListener(WI.AuditTestBase.Event.Remove, this._handleTestRemove, this); >+ if (this.editable) { >+ test.addEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this); >+ test.addEventListener(WI.AuditTestBase.Event.TestChanged, this._handleTestChanged, this); >+ } >+ >+ this.dispatchEventToListeners(WI.AuditTestGroup.Event.TestAdded, {test}); >+ >+ this.checkSupported(); >+ } >+ >+ removeTest(test) >+ { >+ console.assert(WI.auditManager.editing); >+ console.assert(test instanceof WI.AuditTestBase, test); >+ console.assert(this._tests.includes(test), test); >+ >+ if (test[WI.AuditTestBase.DefaultAuditSymbol]) >+ WI.auditManager.removeTest(test); >+ else { >+ console.assert(this.editable); >+ console.assert(test.editable, test); >+ >+ this._tests.remove(test); >+ >+ test.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestCompleted, this); >+ test.removeEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this); >+ test.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestProgress, this); >+ test.removeEventListener(WI.AuditTestBase.Event.Remove, this._handleTestRemove, this); >+ test.removeEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this); >+ test.removeEventListener(WI.AuditTestBase.Event.TestChanged, this._handleTestChanged, this); >+ >+ this.dispatchEventToListeners(WI.AuditTestGroup.Event.TestRemoved, {test}); >+ } >+ >+ this.checkSupported(); >+ } >+ > get supported() > { > return super.supported; >@@ -161,10 +196,13 @@ WI.AuditTestGroup = class AuditTestGroup extends WI.AuditTestBase > > clearResult(options = {}) > { >- let cleared = !!this._result; >- for (let test of this._tests) { >- if (test.clearResult(options)) >- cleared = true; >+ let cleared = !!this.result; >+ >+ if (!options.excludeTests) { >+ for (let test of this._tests) { >+ if (test.clearResult(options)) >+ cleared = true; >+ } > } > > return super.clearResult({ >@@ -196,30 +234,52 @@ WI.AuditTestGroup = class AuditTestGroup extends WI.AuditTestBase > this.dispatchEventToListeners(WI.AuditTestBase.Event.Progress, {index, count}); > } > >- this._updateResult(); >+ this.updateResult(); > } > >- // Private >- >- _updateResult() >+ updateResult() > { > let results = this._tests.map((test) => test.result).filter((result) => !!result); > if (!results.length) > return; > >- this._result = new WI.AuditTestGroupResult(this.name, results, { >+ super.updateResult(new WI.AuditTestGroupResult(this.name, results, { > description: this.description, >- }); >- >- this.dispatchEventToListeners(WI.AuditTestBase.Event.ResultChanged); >+ })); > } > >+ checkSupported(options = {}) >+ { >+ // Reset supported in case this was only unsupported due to a lack of supported sub-tests. >+ let supportedBefore = super.checkSupported(options); >+ >+ if (this._tests) { >+ let hasEnabledSupportedTest = false; >+ >+ this._preventSupportedPropagation = true; >+ for (let test of this._tests) { >+ if (!supportedBefore) >+ test.supported = false; >+ else if (test.supported) >+ hasEnabledSupportedTest = true; >+ } >+ this._preventSupportedPropagation = false; >+ >+ if (!hasEnabledSupportedTest) >+ this.supported = false; >+ } >+ >+ return this.supported; >+ } >+ >+ // Private >+ > _handleTestCompleted(event) > { > if (this._runningState === WI.AuditManager.RunningState.Active) > return; > >- this._updateResult(); >+ this.updateResult(); > this.dispatchEventToListeners(WI.AuditTestBase.Event.Completed); > } > >@@ -229,9 +289,11 @@ WI.AuditTestGroup = class AuditTestGroup extends WI.AuditTestBase > if (event.target.disabled && !enabledTestCount) > this.disabled = true; > else if (!event.target.disabled && enabledTestCount === 1) { >- this._preventDisabledPropagation = true; >- this.disabled = false; >- this._preventDisabledPropagation = false; >+ if (this.checkSupported()) { >+ this._preventDisabledPropagation = true; >+ this.disabled = false; >+ this._preventDisabledPropagation = false; >+ } > } else { > // Don't change `disabled`, as we're currently in an "indeterminate" state. > this.dispatchEventToListeners(WI.AuditTestBase.Event.DisabledChanged); >@@ -262,6 +324,35 @@ WI.AuditTestGroup = class AuditTestGroup extends WI.AuditTestBase > count: walk(this._tests), > }); > } >+ >+ _handleTestRemove(event) >+ { >+ this.removeTest(event.target); >+ } >+ >+ _handleTestSupportedChanged(event) >+ { >+ console.assert(WI.auditManager.editing); >+ >+ if (this._preventSupportedPropagation) >+ return; >+ >+ this.checkSupported(); >+ } >+ >+ _handleTestChanged(event) >+ { >+ console.assert(WI.auditManager.editing); >+ >+ this.clearResult({excludeTests: true}); >+ >+ this.dispatchEventToListeners(WI.AuditTestBase.Event.TestChanged); >+ } > }; > > WI.AuditTestGroup.TypeIdentifier = "test-group"; >+ >+WI.AuditTestGroup.Event = { >+ TestAdded: "audit-test-group-test-added", >+ TestRemoved: "audit-test-group-test-removed", >+}; >diff --git a/Source/WebInspectorUI/UserInterface/Models/AuditTestResultBase.js b/Source/WebInspectorUI/UserInterface/Models/AuditTestResultBase.js >index f94d8a0268522aadf6a01d1839868a587d86a1eb..ac9439cae496c068736eeffa7a3fdce73fe466c5 100644 >--- a/Source/WebInspectorUI/UserInterface/Models/AuditTestResultBase.js >+++ b/Source/WebInspectorUI/UserInterface/Models/AuditTestResultBase.js >@@ -69,6 +69,16 @@ WI.AuditTestResultBase = class AuditTestResultBase > throw WI.NotImplementedError.subclassMustOverride(); > } > >+ get disabled() >+ { >+ return false; >+ } >+ >+ get editable() >+ { >+ return false; >+ } >+ > saveIdentityToCookie(cookie) > { > cookie["audit-" + this.constructor.TypeIdentifier + "-name"] = this._name; >diff --git a/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.css b/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.css >index 56bfd76035be8e1f01c1b1f79420d3ad7bc1e8d8..1318c05abe50fa6485b2210a28d765ba808b48f0 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.css >+++ b/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.css >@@ -33,19 +33,19 @@ > flex-grow: 1; > } > >-.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled):active { >+.sidebar > .panel.navigation.audit .edit-audits:not(.disabled):active { > color: var(--glyph-color-pressed); > } > >-.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated { >+.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated { > color: var(--glyph-color-active); > } > >-.sidebar > .panel.navigation.audit > .content .edit-audits:not(.disabled).activated:active { >+.sidebar > .panel.navigation.audit .edit-audits:not(.disabled).activated:active { > color: var(--glyph-color-active-pressed); > } > >-.sidebar > .panel.navigation.audit > .content .edit-audits.disabled { >+.sidebar > .panel.navigation.audit .edit-audits.disabled { > color: var(--glyph-color-disabled); > } > >@@ -58,9 +58,8 @@ > border-bottom: 1px solid var(--border-color); > } > >-.finish-editing-audits-placeholder.message-text-view .navigation-item-help .navigation-bar { >- padding: 0; >- vertical-align: 0.5px; >+.content-view.audit .message-text-view .navigation-item-help:is(.start-editing-audits, .stop-editing-audits) .navigation-bar { >+ --navigation-item-help-navigation-bar-vertical-align: 0.5px; > } > > .audit-version { >diff --git a/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js b/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js >index cde3fb9cec59b0c35827ddeefd9e54decaa52d78..30bb4333cfd5fa89452bca0529cf41d838f0602a 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js >+++ b/Source/WebInspectorUI/UserInterface/Views/AuditNavigationSidebarPanel.js >@@ -38,26 +38,53 @@ WI.AuditNavigationSidebarPanel = class AuditNavigationSidebarPanel extends WI.Na > > if (WI.auditManager.editing) { > let contentPlaceholder = WI.createMessageTextView(WI.UIString("Editing audits")); >- contentPlaceholder.classList.add("finish-editing-audits-placeholder"); > contentView.element.appendChild(contentPlaceholder); > >- let finishEditingNavigationItem = new WI.ButtonNavigationItem("finish-editing-audits", WI.UIString("Done")); >- finishEditingNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, (event) => { >- WI.auditManager.editing = false; >- }); >+ let descriptionElement = contentPlaceholder.appendChild(document.createElement("div")); >+ descriptionElement.className = "description"; >+ descriptionElement.textContent = WI.UIString("Select an audit in the navigation sidebar to edit it."); > >- let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop editing"), finishEditingNavigationItem); >- contentPlaceholder.appendChild(importHelpElement); >+ let createAuditNavigationItem = new WI.ButtonNavigationItem("create-audit", WI.UIString("Create"), "Images/Plus15.svg", 15, 15); >+ createAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; >+ createAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleCreateButtonNavigationItemClicked, this); >+ >+ let createAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to create a new audit."), createAuditNavigationItem); >+ createAuditHelpElement.classList.add("create-audit"); >+ contentPlaceholder.appendChild(createAuditHelpElement); >+ >+ let stopEditingAuditsNavigationItem = new WI.ButtonNavigationItem("stop-editing-audits", WI.UIString("Done")); >+ stopEditingAuditsNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleEditButtonNavigationItemClicked, this); >+ >+ let stopEditingAuditsHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop editing audits."), stopEditingAuditsNavigationItem); >+ stopEditingAuditsHelpElement.classList.add("stop-editing-audits"); >+ contentPlaceholder.appendChild(stopEditingAuditsHelpElement); > } else { >+ let hasEnabledAudit = WI.auditManager.tests.length && WI.auditManager.tests.some((test) => !test.disabled); >+ > let contentPlaceholder = WI.createMessageTextView(WI.UIString("No audit selected")); > contentView.element.appendChild(contentPlaceholder); > >- let importNavigationItem = new WI.ButtonNavigationItem("import-audit", WI.UIString("Import"), "Images/Import.svg", 15, 15); >- importNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; >- importNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this); >+ if (hasEnabledAudit) { >+ let descriptionElement = contentPlaceholder.appendChild(document.createElement("div")); >+ descriptionElement.className = "description"; >+ descriptionElement.textContent = WI.UIString("Select an audit in the navigation sidebar to view its results."); >+ } > >- let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to import a test or result file"), importNavigationItem); >- contentPlaceholder.appendChild(importHelpElement); >+ let importAuditNavigationItem = new WI.ButtonNavigationItem("import-audit", WI.UIString("Import"), "Images/Import.svg", 15, 15); >+ importAuditNavigationItem.title = WI.UIString("Import audit or result"); >+ importAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; >+ importAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this); >+ >+ let importAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to import an audit or a result."), importAuditNavigationItem); >+ importAuditHelpElement.classList.add("import-audit"); >+ contentPlaceholder.appendChild(importAuditHelpElement); >+ >+ let startEditingAuditsNavigationItem = new WI.ButtonNavigationItem("start-editing-audits", WI.UIString("Edit")); >+ startEditingAuditsNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleEditButtonNavigationItemClicked, this); >+ >+ let startEditingAuditsHelpElement = WI.createNavigationItemHelp(hasEnabledAudit ? WI.UIString("Press %s to start editing audits.") : WI.UIString("Press %s to enable audits."), startEditingAuditsNavigationItem); >+ startEditingAuditsHelpElement.classList.add("start-editing-audits"); >+ contentPlaceholder.appendChild(startEditingAuditsHelpElement); > } > > let versionContainer = contentView.element.appendChild(document.createElement("div")); >@@ -71,6 +98,21 @@ WI.AuditNavigationSidebarPanel = class AuditNavigationSidebarPanel extends WI.Na > this.contentBrowser.showContentView(contentView); > } > >+ // Popover delegate >+ >+ willDismissPopover(popover) >+ { >+ console.assert(popover instanceof WI.CreateAuditPopover, popover); >+ >+ let audit = popover.audit; >+ if (!audit) { >+ InspectorFrontendHost.beep(); >+ return; >+ } >+ >+ WI.auditManager.addTest(audit); >+ } >+ > // Protected > > initialLayout() >@@ -81,29 +123,29 @@ WI.AuditNavigationSidebarPanel = class AuditNavigationSidebarPanel extends WI.Na > > let controlsNavigationBar = new WI.NavigationBar; > >- this._startStopButtonNavigationItem = new WI.ToggleButtonNavigationItem("audit-start-stop", WI.UIString("Start"), WI.UIString("Stop"), "Images/AuditStart.svg", "Images/AuditStop.svg", 13, 13); >- this._startStopButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; >- this._updateStartStopButtonNavigationItemState(); >- this._startStopButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleStartStopButtonNavigationItemClicked, this); >- controlsNavigationBar.addNavigationItem(this._startStopButtonNavigationItem); >- >- controlsNavigationBar.addNavigationItem(new WI.DividerNavigationItem); >- >- let importButtonNavigationItem = new WI.ButtonNavigationItem("audit-import", WI.UIString("Import"), "Images/Import.svg", 15, 15); >+ let importButtonNavigationItem = new WI.ButtonNavigationItem("import-audit", WI.UIString("Import"), "Images/Import.svg", 15, 15); >+ importButtonNavigationItem.title = WI.UIString("Import audit or result"); > importButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; >- importButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; > importButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleImportButtonNavigationItemClicked, this); > controlsNavigationBar.addNavigationItem(importButtonNavigationItem); > >- this.addSubview(controlsNavigationBar); >+ controlsNavigationBar.addNavigationItem(new WI.DividerNavigationItem); > >- let editNavigationbar = new WI.NavigationBar; >+ this._startStopButtonNavigationItem = new WI.ToggleButtonNavigationItem("start-stop-audit", WI.UIString("Start"), WI.UIString("Stop"), "Images/AuditStart.svg", "Images/AuditStop.svg", 13, 13); >+ this._startStopButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; >+ this._startStopButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleStartStopButtonNavigationItemClicked, this); >+ controlsNavigationBar.addNavigationItem(this._startStopButtonNavigationItem); >+ >+ this._createButtonNavigationItem = new WI.ButtonNavigationItem("create-audit", WI.UIString("Create"), "Images/Plus15.svg", 15, 15); >+ this._createButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; >+ this._createButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleCreateButtonNavigationItemClicked, this); >+ controlsNavigationBar.addNavigationItem(this._createButtonNavigationItem); >+ >+ this.addSubview(controlsNavigationBar); > > this._editButtonNavigationItem = new WI.ActivateButtonNavigationItem("edit-audits", WI.UIString("Edit"), WI.UIString("Done")); > this._editButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleEditButtonNavigationItemClicked, this); >- editNavigationbar.addNavigationItem(this._editButtonNavigationItem); >- >- this.contentView.addSubview(editNavigationbar); >+ this.filterBar.addFilterNavigationItem(this._editButtonNavigationItem); > > for (let test of WI.auditManager.tests) > this._addTest(test); >@@ -112,7 +154,14 @@ WI.AuditNavigationSidebarPanel = class AuditNavigationSidebarPanel extends WI.Na > this._addResult(result, i); > }); > >+ this._updateControlNavigationItems(); >+ this._updateEditNavigationItems(); >+ this._updateNoAuditsPlaceholder(); >+ >+ WI.AuditTestGroup.addEventListener(WI.AuditTestGroup.Event.TestRemoved, this._handleAuditTestRemoved, this); >+ > WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleAuditManagerEditingChanged, this); >+ WI.auditManager.addEventListener(WI.AuditManager.Event.RunningStateChanged, this._handleAuditManagerRunningStateChanged, this); > WI.auditManager.addEventListener(WI.AuditManager.Event.TestAdded, this._handleAuditTestAdded, this); > WI.auditManager.addEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditTestCompleted, this); > WI.auditManager.addEventListener(WI.AuditManager.Event.TestRemoved, this._handleAuditTestRemoved, this); >@@ -165,10 +214,6 @@ WI.AuditNavigationSidebarPanel = class AuditNavigationSidebarPanel extends WI.Na > this._resultsFolderTreeElement.hidden = !this._resultsFolderTreeElement.children.length; > } else > this.contentTreeOutline.appendChild(treeElement); >- >- this._updateStartStopButtonNavigationItemState(); >- this._updateEditButtonNavigationItemState(); >- this._updateNoAuditsPlaceholder(); > } > > _addResult(result, index) >@@ -193,22 +238,26 @@ WI.AuditNavigationSidebarPanel = class AuditNavigationSidebarPanel extends WI.Na > > for (let resultItem of result) > resultFolderTreeElement.appendChild(new WI.AuditTreeElement(resultItem)); >- >- this._updateStartStopButtonNavigationItemState(); >- this._updateEditButtonNavigationItemState(); > } > >- _updateStartStopButtonNavigationItemState() >+ _updateControlNavigationItems() > { >+ let editing = WI.auditManager.editing; >+ > this._startStopButtonNavigationItem.toggled = WI.auditManager.runningState === WI.AuditManager.RunningState.Active || WI.auditManager.runningState === WI.AuditManager.RunningState.Stopping; > this._startStopButtonNavigationItem.enabled = WI.auditManager.tests.some((test) => !test.disabled) && (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive || WI.auditManager.runningState === WI.AuditManager.RunningState.Active); >+ this._startStopButtonNavigationItem.hidden = editing; >+ >+ this._createButtonNavigationItem.hidden = !editing; > } > >- _updateEditButtonNavigationItemState() >+ _updateEditNavigationItems() > { >- this._editButtonNavigationItem.label = WI.auditManager.editing ? this._editButtonNavigationItem.activatedToolTip : this._editButtonNavigationItem.defaultToolTip; >- this._editButtonNavigationItem.activated = WI.auditManager.editing; >- this._editButtonNavigationItem.enabled = WI.auditManager.tests.length && (WI.auditManager.editing || WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive); >+ let editing = WI.auditManager.editing; >+ >+ this._editButtonNavigationItem.label = editing ? this._editButtonNavigationItem.activatedToolTip : this._editButtonNavigationItem.defaultToolTip; >+ this._editButtonNavigationItem.activated = editing; >+ this._editButtonNavigationItem.enabled = editing || WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive; > } > > _updateNoAuditsPlaceholder() >@@ -231,59 +280,68 @@ WI.AuditNavigationSidebarPanel = class AuditNavigationSidebarPanel extends WI.Na > > _handleAuditManagerEditingChanged(event) > { >- if (WI.auditManager.editing) { >- console.assert(!this._selectedTreeElementBeforeEditing); >- this._selectedTreeElementBeforeEditing = this.contentTreeOutline.selectedTreeElement; >- if (this._selectedTreeElementBeforeEditing) >- this._selectedTreeElementBeforeEditing.deselect(); >- } else if (this._selectedTreeElementBeforeEditing) { >- if (this.contentTreeOutline.selectedTreeElement === this._selectedTreeElementBeforeEditing) { >- const suppressNotification = true; >- this._selectedTreeElementBeforeEditing.deselect(suppressNotification); >+ let previousSelectedTreeElement = this.contentTreeOutline.selectedTreeElement; >+ if (previousSelectedTreeElement) { >+ if (WI.auditManager.editing) { >+ if (!(previousSelectedTreeElement.representedObject instanceof WI.AuditTestBase)) >+ previousSelectedTreeElement.deselect(); >+ } else { >+ if (previousSelectedTreeElement.representedObject.disabled) >+ previousSelectedTreeElement.deselect(); > } >- if (!(this._selectedTreeElementBeforeEditing.representedObject instanceof WI.AuditTestBase) || !this._selectedTreeElementBeforeEditing.representedObject.disabled) { >- const omitFocus = false; >- const selectedByUser = true; >- this._selectedTreeElementBeforeEditing.select(omitFocus, selectedByUser); >- } >- this._selectedTreeElementBeforeEditing = null; > } > >+ this.updateFilter(); >+ > if (!this.contentTreeOutline.selectedTreeElement) > this.showDefaultContentView(); > >- this._updateStartStopButtonNavigationItemState(); >- this._updateEditButtonNavigationItemState(); >+ this._updateControlNavigationItems(); >+ this._updateEditNavigationItems(); >+ this._updateNoAuditsPlaceholder(); >+ } > >- this.updateFilter(); >+ _handleAuditManagerRunningStateChanged(event) >+ { >+ this._updateControlNavigationItems(); >+ this._updateEditNavigationItems(); > } > > _handleAuditTestAdded(event) > { >- this._addTest(event.data.test); >+ let {test} = event.data; >+ >+ this._addTest(test); >+ >+ this._updateControlNavigationItems(); >+ this._updateNoAuditsPlaceholder(); > } > > _handleAuditTestCompleted(event) > { > let {result, index} = event.data; > this._addResult(result, index); >+ >+ this._updateControlNavigationItems(); >+ this._updateEditNavigationItems(); > } > > _handleAuditTestRemoved(event) > { >- let {test} = event.data; >- let treeElement = this.treeElementForRepresentedObject(test); >- this.contentTreeOutline.removeChild(treeElement); >+ console.assert(WI.auditManager.editing); > >- this._updateStartStopButtonNavigationItemState(); >- this._updateEditButtonNavigationItemState(); >- this._updateNoAuditsPlaceholder(); >+ let {test} = event.data; >+ >+ let treeElement = this.treeElementForRepresentedObject(test); >+ treeElement.parent.removeChild(treeElement); >+ >+ this._updateControlNavigationItems(); > } > > _handleAuditTestScheduled(event) > { >- this._updateStartStopButtonNavigationItemState(); >- this._updateEditButtonNavigationItemState(); >+ this._updateControlNavigationItems(); >+ this._updateEditNavigationItems(); > } > > _treeSelectionDidChange(event) >@@ -297,9 +355,6 @@ WI.AuditNavigationSidebarPanel = class AuditNavigationSidebarPanel extends WI.Na > return; > } > >- if (WI.auditManager.editing) >- return; >- > let representedObject = treeElement.representedObject; > if (representedObject instanceof WI.AuditTestCase || representedObject instanceof WI.AuditTestGroup > || representedObject instanceof WI.AuditTestCaseResult || representedObject instanceof WI.AuditTestGroupResult) { >@@ -316,8 +371,14 @@ WI.AuditNavigationSidebarPanel = class AuditNavigationSidebarPanel extends WI.Na > WI.auditManager.start(); > else if (WI.auditManager.runningState === WI.AuditManager.RunningState.Active) > WI.auditManager.stop(); >+ } > >- this._updateStartStopButtonNavigationItemState(); >+ _handleCreateButtonNavigationItemClicked(event) >+ { >+ console.assert(WI.auditManager.editing); >+ >+ let popover = new WI.CreateAuditPopover(this); >+ popover.show(this._createButtonNavigationItem.element, [WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X, WI.RectEdge.MIN_X]); > } > > _handleImportButtonNavigationItemClicked(event) >diff --git a/Source/WebInspectorUI/UserInterface/Views/AuditTabContentView.js b/Source/WebInspectorUI/UserInterface/Views/AuditTabContentView.js >index 5416a20143f681676282d04d08562836d10f6f44..c8f8e18c88b59d844a22ae59ca03edf24f4d3c00 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/AuditTabContentView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/AuditTabContentView.js >@@ -111,7 +111,7 @@ WI.AuditTabContentView = class AuditTabContentView extends WI.ContentBrowserTabC > super.initialLayout(); > > let dropZoneView = new WI.DropZoneView(this); >- dropZoneView.text = WI.UIString("Import Audit"); >+ dropZoneView.text = WI.UIString("Import Audit or Result"); > dropZoneView.targetElement = this.element; > this.addSubview(dropZoneView); > >diff --git a/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css b/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css >index ccad54c61e30b846b709778bce6bf29abcad60de..3f189a8ad4df95b0deb464ed0479835a7e5a8cc4 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css >+++ b/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.css >@@ -23,7 +23,15 @@ > * THE POSSIBILITY OF SUCH DAMAGE. > */ > >+.content-view-container > .content-view.audit-test-case { >+ display: flex; >+ flex-direction: column; >+ height: 100%; >+ overflow: hidden; >+} >+ > .content-view-container > .content-view.audit-test-case > header { >+ flex-shrink: 0; > position: sticky; > top: -1px; > z-index: var(--z-index-header); >@@ -33,10 +41,28 @@ > -webkit-backdrop-filter: blur(20px); > } > >-.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view):first-child { >+.content-view-container > .content-view.audit-test-case.manager-editing > header h1 > img { >+ width: 0.75em; >+ min-width: 12px; >+ height: 0.75em; >+ min-height: 12px; >+ margin: 0.125em; >+ margin-inline-end: 0.375em; >+} >+ >+.content-view-container > .content-view.audit-test-case > section > :not(.message-text-view, .editor):first-child { > margin-top: var(--audit-test-vertical-space); > } > >+.content-view-container > .content-view.audit-test-case > section { >+ overflow-y: scroll; >+} >+ >+.content-view-container > .content-view.audit-test-case > section, >+.content-view-container > .content-view.audit-test-case > section :is(.editor, .CodeMirror) { >+ height: 100%; >+} >+ > .content-view.audit-test-case > header { > -webkit-padding-end: calc(var(--audit-test-horizontal-space) / 2); > } >@@ -54,6 +80,25 @@ > -webkit-margin-end: 0.25em; > } > >+.content-view.audit-test-case.manager-editing > header h1 > img { >+ padding: 0.125em; >+ border: 1px solid black; >+ border-radius: 50%; >+} >+ >+.content-view.audit-test-case.manager-editing.disabled:not(.editable) > header h1 > img { >+ opacity: 0.3; >+ pointer-events: none; >+} >+ >+.content-view.audit-test-case.manager-editing.editable > header h1 > img:hover { >+ background-color: var(--background-color-alternate); >+} >+ >+.content-view.audit-test-case.manager-editing.editable > header h1 > img:active { >+ background-color: var(--background-color-selected); >+} >+ > .content-view.audit-test-case > header > .metadata { > display: flex; > align-items: center; >@@ -83,16 +128,16 @@ > font-weight: bold; > } > >-.content-view.audit-test-case > section > :not(.message-text-view) { >+.content-view.audit-test-case > section > :not(.message-text-view, .editor) { > margin-right: var(--audit-test-horizontal-space); > margin-left: var(--audit-test-horizontal-space); > } > >-.content-view.audit-test-case > section > :not(.message-text-view):last-child { >+.content-view.audit-test-case > section > :not(.message-text-view, .editor):last-child { > margin-bottom: var(--audit-test-vertical-space); > } > >-.content-view.audit-test-case > section > :not(.message-text-view) + :not(.message-text-view) { >+.content-view.audit-test-case > section > :not(.message-text-view, .editor) + :not(.message-text-view, .editor) { > margin-top: var(--audit-test-vertical-space); > } > >@@ -131,12 +176,13 @@ > width: 100%; > } > >-.content-view.audit-test-case > section .CodeMirror { >- width: 100%; >- height: auto; >-} >- > .content-view.audit-test-case > section .mark { > background-color: hsla(53, 83%, 53%, 0.2); > border-bottom: 1px solid hsl(47, 82%, 60%); > } >+ >+@media (prefers-color-scheme: dark) { >+ .content-view.audit-test-case.manager-editing > header h1 > img { >+ filter: var(--filter-invert); >+ } >+} >diff --git a/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js b/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js >index 4a1f3ae691f35cc95025712d0ec26dbb08cb98f2..f4b5923f4a2e91be027980cf67dea7f7858d6630 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/AuditTestCaseContentView.js >@@ -47,16 +47,20 @@ WI.AuditTestCaseContentView = class AuditTestCaseContentView extends WI.AuditTes > let informationContainer = this.headerView.element.appendChild(document.createElement("div")); > informationContainer.classList.add("information"); > >- let nameElement = informationContainer.appendChild(document.createElement("h1")); >+ let nameContainer = informationContainer.appendChild(document.createElement("h1")); > >- this._resultImageElement = nameElement.appendChild(document.createElement("img")); >+ this._resultImageElement = nameContainer.appendChild(document.createElement("img")); >+ this._resultImageElement.addEventListener("click", (event) => { >+ if (WI.auditManager.editing) >+ this.representedObject.remove(); >+ }); > >- nameElement.appendChild(document.createTextNode(this.representedObject.name)); >+ nameContainer.appendChild(this.createNameElement("span")); > >- if (this.representedObject.description) { >- let descriptionElement = informationContainer.appendChild(document.createElement("p")); >- descriptionElement.textContent = this.representedObject.description; >- } >+ informationContainer.appendChild(this.createDescriptionElement("p")); >+ >+ if (this.representedObject instanceof WI.AuditTestCase) >+ informationContainer.appendChild(this.createControlsElement()); > > this._metadataElement = this.headerView.element.appendChild(document.createElement("div")); > this._metadataElement.classList.add("metadata"); >@@ -69,11 +73,41 @@ WI.AuditTestCaseContentView = class AuditTestCaseContentView extends WI.AuditTes > > super.layout(); > >- this._resultImageElement.src = "Images/AuditTestNoResult.svg"; > this._metadataElement.removeChildren(); >- > this.contentView.element.removeChildren(); > >+ let editable = this.representedObject.editable; >+ let editing = WI.auditManager.editing; >+ >+ this._resultImageElement.src = editing ? "Images/CloseLarge.svg" : "Images/AuditTestNoResult.svg"; >+ this._resultImageElement.title = editing ? WI.UIString("Delete audit") : WI.UIString("Not yet run"); >+ >+ if (editing) { >+ let testEditorElement = this.contentView.element.appendChild(document.createElement("div")); >+ testEditorElement.className = "editor"; >+ >+ // Give the rest of the view a chance to load. >+ setTimeout(() => { >+ let testCodeMirror = WI.CodeMirrorEditor.create(testEditorElement, { >+ autoCloseBrackets: true, >+ lineNumbers: true, >+ lineWrapping: true, >+ matchBrackets: true, >+ mode: "text/javascript", >+ readOnly: editable ? false : "nocursor", >+ styleSelectedText: true, >+ value: this.representedObject.test, >+ }); >+ >+ if (editable) { >+ testCodeMirror.on("blur", (event) => { >+ this.representedObject.test = testCodeMirror.getValue().trim(); >+ }); >+ } >+ }); >+ return; >+ } >+ > let result = this.representedObject.result; > if (!result) { > if (this.representedObject.runningState === WI.AuditManager.RunningState.Inactive) >@@ -86,16 +120,22 @@ WI.AuditTestCaseContentView = class AuditTestCaseContentView extends WI.AuditTes > return; > } > >- if (result.didError) >+ if (result.didError) { > this._resultImageElement.src = "Images/AuditTestError.svg"; >- else if (result.didFail) >+ this._resultImageElement.title = WI.UIString("Error"); >+ } else if (result.didFail) { > this._resultImageElement.src = "Images/AuditTestFail.svg"; >- else if (result.didWarn) >+ this._resultImageElement.title = WI.UIString("Fail"); >+ } else if (result.didWarn) { > this._resultImageElement.src = "Images/AuditTestWarn.svg"; >- else if (result.didPass) >+ this._resultImageElement.title = WI.UIString("Warn"); >+ } else if (result.didPass) { > this._resultImageElement.src = "Images/AuditTestPass.svg"; >- else if (result.unsupported) >+ this._resultImageElement.title = WI.UIString("Pass"); >+ } else if (result.unsupported) { > this._resultImageElement.src = "Images/AuditTestUnsupported.svg"; >+ this._resultImageElement.title = WI.UIString("Unsupported"); >+ } > > let metadata = result.metadata; > if (metadata) { >@@ -299,6 +339,15 @@ WI.AuditTestCaseContentView = class AuditTestCaseContentView extends WI.AuditTes > > let spinner = new WI.IndeterminateProgressSpinner; > this.placeholderElement.appendChild(spinner.element); >+ >+ let stopAuditNavigationItem = new WI.ButtonNavigationItem("stop-audit", WI.UIString("Stop"), "Images/AuditStop.svg", 13, 13); >+ stopAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; >+ stopAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, (event) => { >+ WI.auditManager.stop(); >+ }, WI.auditManager); >+ >+ let stopAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop running."), stopAuditNavigationItem); >+ this.placeholderElement.appendChild(stopAuditHelpElement); > } > > super.showRunningPlaceholder(); >diff --git a/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.css b/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.css >index 40c2ade04123aa9085b73066beddba11c2a0cd8b..14a3f14b78928b2ebb878a662cb7f218682d0c07 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.css >+++ b/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.css >@@ -48,10 +48,27 @@ > --audit-test-header-background-color: hsla(0, 0%, 98%, 0.7); > } > >+.content-view.audit-test.disabled:not(.manager-editing) { >+ display: none; >+} >+ > .content-view.audit-test h1 { > margin: 0; > } > >+.content-view.audit-test.manager-editing .editor:not(:empty) { >+ width: 100%; >+} >+ >+.content-view.audit-test.manager-editing :is(.content-view.audit-test, header) .editor:not(:empty) { >+ border: 1px solid var(--border-color); >+} >+ >+.content-view.audit-test .CodeMirror { >+ width: 100%; >+ height: auto; >+} >+ > .content-view.audit-test > header { > display: flex; > align-items: center; >@@ -70,6 +87,57 @@ > margin: 4px 0 0; > } > >+.content-view.audit-test:not(.manager-editing) > header .description:empty { >+ display: none; >+} >+ >+.content-view.audit-test > header .controls { >+ margin-top: 8px; >+} >+ >+.content-view.audit-test > header .controls .row + .row { >+ margin-top: 4px; >+} >+ >+.content-view.audit-test > header .controls .setup label { >+ display: flex; >+} >+ >+.content-view.audit-test > header .controls .setup .editor { >+ margin-inline-start: 4px; >+} >+ >+.content-view.audit-test:not(.manager-editing) > header .controls { >+ display: none; >+} >+ >+.content-view.audit-test > header .controls > .supports input[type="number"] { >+ text-align: end; >+} >+ >+.content-view.audit-test > header .controls > .supports .warning { >+ margin-inline-start: 4px; >+ color: var(--text-color-secondary); >+} >+ >+.content-view.audit-test > header .controls > .supports .warning:not(:empty)::before { >+ display: inline-block; >+ width: 1em; >+ margin-inline-end: 2px; >+ vertical-align: -1px; >+ content: url(../Images/Warning.svg); >+} >+ >+.content-view.audit-test > header :is(.name, .description):not([contenteditable]) { >+ padding: 1px; >+ outline: none; >+} >+ >+.content-view.audit-test.manager-editing > header :is(.name, .description)[contenteditable] { >+ border: 1px solid var(--border-color); >+ -webkit-user-select: text; >+} >+ > .content-view.audit-test .audit-test.filtered, > .content-view.audit-test .audit-test .message-text-view { > display: none; >@@ -83,6 +151,10 @@ > background-color: var(--background-color-content); > } > >+.content-view.audit-test > section > .message-text-view > :is(progress, .indeterminate-progress-spinner) { >+ margin-bottom: 8px; >+} >+ > .content-view.audit-test.showing-placeholder { > display: flex; > flex-direction: column; >diff --git a/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js b/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js >index 0597e10c29522131a694bea24665cb1648a3e40d..936a6e131e63fb3c2df72c946385783971d5b8d3 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/AuditTestContentView.js >@@ -35,18 +35,34 @@ WI.AuditTestContentView = class AuditTestContentView extends WI.ContentView > console.assert(this.constructor !== WI.AuditTestContentView && this instanceof WI.AuditTestContentView); > > this.element.classList.add("audit-test"); >+ if (this.representedObject.editable) >+ this.element.classList.add("editable"); > >- this._exportButtonNavigationItem = new WI.ButtonNavigationItem("audit-export", WI.UIString("Export"), "Images/Export.svg", 15, 15); >- this._exportButtonNavigationItem.tooltip = WI.UIString("Export result (%s)").format(WI.saveKeyboardShortcut.displayName); >- this._exportButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; >- this._exportButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; >- this._exportButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleExportButtonNavigationItemClicked, this); >- this._updateExportButtonNavigationItemState(); >+ if (this.representedObject instanceof WI.AuditTestBase) { >+ this._exportTestButtonNavigationItem = new WI.ButtonNavigationItem("audit-export-test", WI.UIString("Export Audit"), "Images/Export.svg", 15, 15); >+ this._exportTestButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; >+ this._exportTestButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; >+ this._exportTestButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleExportTestButtonNavigationItemClicked, this); >+ } >+ >+ this._exportResultButtonNavigationItem = new WI.ButtonNavigationItem("audit-export-result", WI.UIString("Export Result"), "Images/Export.svg", 15, 15); >+ this._exportResultButtonNavigationItem.tooltip = WI.UIString("Export Result (%s)").format(WI.saveKeyboardShortcut.displayName); >+ this._exportResultButtonNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; >+ this._exportResultButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low; >+ this._exportResultButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleExportResultButtonNavigationItemClicked, this); >+ >+ this._updateExportNavigationItems(); > > this._headerView = new WI.View(document.createElement("header")); > this._contentView = new WI.View(document.createElement("section")); > this._placeholderElement = null; > >+ this._nameElement = null; >+ this._descriptionElement = null; >+ >+ this._supportsInputElement = null; >+ this._supportsWarningElement = null; >+ > this._shownResult = null; > } > >@@ -54,7 +70,11 @@ WI.AuditTestContentView = class AuditTestContentView extends WI.ContentView > > get navigationItems() > { >- return [this._exportButtonNavigationItem]; >+ let navigationItems = []; >+ if (this._exportTestButtonNavigationItem) >+ navigationItems.push(this._exportTestButtonNavigationItem); >+ navigationItems.push(this._exportResultButtonNavigationItem); >+ return navigationItems; > } > > // Protected >@@ -79,6 +99,97 @@ WI.AuditTestContentView = class AuditTestContentView extends WI.ContentView > return this.representedObject.result; > } > >+ createNameElement(tagName) >+ { >+ console.assert(!this._nameElement); >+ >+ let editable = this.representedObject.editable; >+ >+ this._nameElement = document.createElement(tagName); >+ this._nameElement.textContent = this.representedObject.name; >+ this._nameElement.className = "name"; >+ >+ if (editable) { >+ this._nameElement.addEventListener("input", (event) => { >+ console.assert(!WI.auditManager.editing); >+ >+ this.representedObject.name = this._nameElement.textContent; >+ }); >+ } >+ >+ return this._nameElement; >+ } >+ >+ createDescriptionElement(tagName) >+ { >+ console.assert(!this._descriptionElement); >+ >+ let editable = this.representedObject.editable; >+ >+ this._descriptionElement = document.createElement(tagName); >+ this._descriptionElement.textContent = this.representedObject.description; >+ this._descriptionElement.className = "description"; >+ >+ if (editable) { >+ this._descriptionElement.addEventListener("input", (event) => { >+ console.assert(!WI.auditManager.editing); >+ >+ this.representedObject.description = this._descriptionElement.textContent; >+ }); >+ } >+ >+ return this._descriptionElement; >+ } >+ >+ createControlsElement() >+ { >+ console.assert(this.representedObject instanceof WI.AuditTestBase); >+ console.assert(!this._supportsInputElement); >+ console.assert(!this._supportsWarningElement); >+ >+ let editable = this.representedObject.editable; >+ >+ let controlsContainerElement = document.createElement("div"); >+ controlsContainerElement.className = "controls"; >+ >+ let supportsContinerElement = controlsContainerElement.appendChild(document.createElement("div")); >+ supportsContinerElement.className = "row supports"; >+ >+ let supportsLabelElement = supportsContinerElement.appendChild(document.createElement("label")); >+ supportsLabelElement.textContent = WI.UIString("Supports: "); >+ >+ this._supportsInputElement = supportsLabelElement.appendChild(document.createElement("input")); >+ this._supportsInputElement.type = "number"; >+ this._supportsInputElement.disabled = !editable; >+ this._supportsInputElement.min = 0; >+ this._supportsInputElement.placeholder = Math.min(WI.AuditTestBase.Version, InspectorBackend.hasDomain("Audit") ? InspectorBackend.getVersion("Audit") : Infinity); >+ if (!isNaN(this.representedObject.supports)) >+ this._supportsInputElement.value = this.representedObject.supports; >+ >+ this._supportsWarningElement = supportsContinerElement.appendChild(document.createElement("span")); >+ this._supportsWarningElement.className = "warning"; >+ >+ if (WI.auditManager.isTopLevel(this.representedObject)) { >+ let setupContainerElement = controlsContainerElement.appendChild(document.createElement("div")); >+ setupContainerElement.className = "row setup"; >+ >+ let setupLabelElement = setupContainerElement.appendChild(document.createElement("label")); >+ setupLabelElement.textContent = WI.UIString("Setup: "); >+ >+ this._setupEditorElement = setupLabelElement.appendChild(document.createElement("div")); >+ } >+ >+ if (editable) { >+ this._supportsInputElement.addEventListener("input", (event) => { >+ this.representedObject.supports = parseInt(this._supportsInputElement.value); >+ >+ this._updateSupportsInputState(); >+ }); >+ } >+ >+ return controlsContainerElement; >+ } >+ > initialLayout() > { > super.initialLayout(); >@@ -91,8 +202,26 @@ WI.AuditTestContentView = class AuditTestContentView extends WI.ContentView > { > super.layout(); > >+ let editing = WI.auditManager.editing; >+ let editable = this.representedObject.editable; >+ >+ this.element.classList.toggle("disabled", this.representedObject.disabled); >+ this.element.classList.toggle("manager-editing", editing); >+ >+ if (editable) { >+ let contentEditable = editing ? "plaintext-only" : "inherit"; >+ this._nameElement.contentEditable = contentEditable; >+ this._descriptionElement.contentEditable = contentEditable; >+ } >+ >+ if (editing) { >+ this._updateSupportsInputState(); >+ this._createSetupEditor(); >+ } else >+ this._setupEditorElement?.removeChildren(); >+ > this.hidePlaceholder(); >- this._updateExportButtonNavigationItemState(); >+ this._updateExportNavigationItems(); > } > > shown() >@@ -101,17 +230,28 @@ WI.AuditTestContentView = class AuditTestContentView extends WI.ContentView > > if (this.representedObject instanceof WI.AuditTestBase) { > this.representedObject.addEventListener(WI.AuditTestBase.Event.Completed, this._handleTestChanged, this); >+ this.representedObject.addEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this); > this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestChanged, this); > this.representedObject.addEventListener(WI.AuditTestBase.Event.ResultChanged, this.handleResultChanged, this); > this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestChanged, this); > this.representedObject.addEventListener(WI.AuditTestBase.Event.Stopping, this._handleTestChanged, this); >+ >+ WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleEditingChanged, this); > } > } > > hidden() > { >- if (this.representedObject instanceof WI.AuditTestBase) >- this.representedObject.removeEventListener(null, null, this); >+ if (this.representedObject instanceof WI.AuditTestBase) { >+ this.representedObject.removeEventListener(WI.AuditTestBase.Event.Completed, this._handleTestChanged, this); >+ this.representedObject.removeEventListener(WI.AuditTestBase.Event.DisabledChanged, this._handleTestDisabledChanged, this); >+ this.representedObject.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestChanged, this); >+ this.representedObject.removeEventListener(WI.AuditTestBase.Event.ResultChanged, this.handleResultChanged, this); >+ this.representedObject.removeEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestChanged, this); >+ this.representedObject.removeEventListener(WI.AuditTestBase.Event.Stopping, this._handleTestChanged, this); >+ >+ WI.auditManager.removeEventListener(WI.AuditManager.Event.EditingChanged, this._handleEditingChanged, this); >+ } > > super.hidden(); > } >@@ -120,7 +260,8 @@ WI.AuditTestContentView = class AuditTestContentView extends WI.ContentView > { > // Overridden by sub-classes. > >- this.needsLayout(); >+ if (!WI.auditManager.editing) >+ this.needsLayout(); > } > > get placeholderElement() >@@ -169,7 +310,7 @@ WI.AuditTestContentView = class AuditTestContentView extends WI.ContentView > WI.auditManager.start([this.representedObject]); > }); > >- let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to start running the audit"), startNavigationItem); >+ let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to start running the audit."), startNavigationItem); > this.placeholderElement.appendChild(importHelpElement); > } > >@@ -278,9 +419,61 @@ WI.AuditTestContentView = class AuditTestContentView extends WI.ContentView > WI.auditManager.export(this.representedObject.result); > } > >- _updateExportButtonNavigationItemState() >+ _updateExportNavigationItems() > { >- this._exportButtonNavigationItem.enabled = !!this.representedObject.result; >+ let editing = WI.auditManager.editing; >+ >+ if (this._exportTestButtonNavigationItem) >+ this._exportTestButtonNavigationItem.enabled = !editing; >+ >+ this._exportResultButtonNavigationItem.enabled = !editing && this.representedObject.result; >+ } >+ >+ _updateSupportsInputState(event) >+ { >+ console.assert(WI.auditManager.editing); >+ >+ this._supportsInputElement.autosize(4); >+ >+ this._supportsWarningElement.removeChildren(); >+ if (this.representedObject.supports > WI.AuditTestBase.Version) >+ this._supportsWarningElement.textContent = WI.UIString("too new to run in this Web Inspector"); >+ else if (InspectorBackend.hasDomain("Audit") && this._supports > InspectorBackend.getVersion("Audit")) >+ this._supportsWarningElement.textContent = WI.UIString("too new to run in the inspected page"); >+ } >+ >+ _createSetupEditor() >+ { >+ if (!this._setupEditorElement) >+ return; >+ >+ let editable = this.representedObject.editable; >+ >+ let setupEditorElement = document.createElement(this._setupEditorElement.nodeName); >+ setupEditorElement.className = "editor"; >+ >+ // Give the rest of the view a chance to load. >+ setTimeout(() => { >+ let setupCodeMirror = WI.CodeMirrorEditor.create(setupEditorElement, { >+ autoCloseBrackets: true, >+ lineNumbers: true, >+ lineWrapping: true, >+ matchBrackets: true, >+ mode: "text/javascript", >+ readOnly: editable ? false : "nocursor", >+ styleSelectedText: true, >+ value: this.representedObject.setup, >+ }); >+ >+ if (editable) { >+ setupCodeMirror.on("blur", (event) => { >+ this.representedObject.setup = setupCodeMirror.getValue().trim(); >+ }); >+ } >+ }); >+ >+ this._setupEditorElement.parentNode.replaceChild(setupEditorElement, this._setupEditorElement); >+ this._setupEditorElement = setupEditorElement; > } > > _showPlaceholder() >@@ -289,7 +482,12 @@ WI.AuditTestContentView = class AuditTestContentView extends WI.ContentView > this.contentView.element.appendChild(this.placeholderElement); > } > >- _handleExportButtonNavigationItemClicked(event) >+ _handleExportTestButtonNavigationItemClicked(event) >+ { >+ WI.auditManager.export(this.representedObject); >+ } >+ >+ _handleExportResultButtonNavigationItemClicked(event) > { > this._exportResult(); > } >@@ -298,4 +496,18 @@ WI.AuditTestContentView = class AuditTestContentView extends WI.ContentView > { > this.needsLayout(); > } >+ >+ _handleTestDisabledChanged(event) >+ { >+ console.assert(WI.auditManager.editing); >+ >+ this.element.classList.toggle("disabled", this.representedObject.disabled); >+ } >+ >+ _handleEditingChanged(event) >+ { >+ this.needsLayout(); >+ >+ this._updateExportNavigationItems(); >+ } > }; >diff --git a/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.css b/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.css >index 90abcf586dc8f597164e861368f19bb7696e669e..3c94db365819517288c22a65266a8fbcb660f29b 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.css >+++ b/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.css >@@ -50,7 +50,8 @@ > -webkit-backdrop-filter: blur(20px); > } > >-.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case { >+.content-view.audit-test-group.contains-test-case + .audit-test-group.contains-test-case, >+.content-view.audit-test-group + .content-view.audit-test-case { > border-top: 1px solid var(--border-color); > } > >diff --git a/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.js b/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.js >index 263f3bf43d6012259224e09a4e58043f3820ea2a..861a674d07829bfdc8f6926765c57524d0249fa8 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/AuditTestGroupContentView.js >@@ -32,14 +32,60 @@ WI.AuditTestGroupContentView = class AuditTestGroupContentView extends WI.AuditT > super(representedObject); > > this.element.classList.add("audit-test-group"); >- this.element.classList.toggle("contains-test-case", this._subobjects().some((test) => test instanceof WI.AuditTestCase || test instanceof WI.AuditTestCaseResult)); >- this.element.classList.toggle("contains-test-group", this._subobjects().some((test) => test instanceof WI.AuditTestGroup || test instanceof WI.AuditTestGroupResult)); > > this._levelScopeBar = null; >+ >+ this._viewForSubobject = new Map; >+ } >+ >+ // Popover delegate >+ >+ willDismissPopover(popover) >+ { >+ console.assert(popover instanceof WI.CreateAuditPopover, popover); >+ >+ let audit = popover.audit; >+ if (!audit) { >+ InspectorFrontendHost.beep(); >+ return; >+ } >+ >+ this.representedObject.addTest(audit); > } > > // Protected > >+ createControlsElement() >+ { >+ let controlsContainerElement = super.createControlsElement(); >+ >+ let editable = this.representedObject.editable; >+ >+ let rowElement = controlsContainerElement.appendChild(document.createElement("div")); >+ rowElement.className = "row"; >+ >+ let removeThisButtonElement = rowElement.appendChild(document.createElement("button")); >+ removeThisButtonElement.disabled = !editable; >+ removeThisButtonElement.textContent = WI.UIString("Delete Group"); >+ removeThisButtonElement.addEventListener("click", (event) => { >+ console.assert(WI.auditManager.editing); >+ >+ this.representedObject.remove(); >+ }); >+ >+ let addTestCaseButtonElement = rowElement.appendChild(document.createElement("button")); >+ addTestCaseButtonElement.disabled = !editable; >+ addTestCaseButtonElement.textContent = WI.UIString("Add Test Case"); >+ addTestCaseButtonElement.addEventListener("click", (event) => { >+ console.assert(WI.auditManager.editing); >+ >+ let popover = new WI.CreateAuditPopover(this); >+ popover.show(addTestCaseButtonElement, [WI.RectEdge.MAX_Y, WI.RectEdge.MAX_X, WI.RectEdge.MIN_X]); >+ }); >+ >+ return controlsContainerElement; >+ } >+ > initialLayout() > { > super.initialLayout(); >@@ -47,13 +93,14 @@ WI.AuditTestGroupContentView = class AuditTestGroupContentView extends WI.AuditT > let informationContainer = this.headerView.element.appendChild(document.createElement("div")); > informationContainer.classList.add("information"); > >- let nameElement = informationContainer.appendChild(document.createElement("h1")); >- nameElement.textContent = this.representedObject.name; >+ let nameContainer = informationContainer.appendChild(document.createElement("h1")); > >- if (this.representedObject.description) { >- let descriptionElement = informationContainer.appendChild(document.createElement("p")); >- descriptionElement.textContent = this.representedObject.description; >- } >+ nameContainer.appendChild(this.createNameElement("span")); >+ >+ informationContainer.appendChild(this.createDescriptionElement("p")); >+ >+ if (this.representedObject instanceof WI.AuditTestGroup) >+ informationContainer.appendChild(this.createControlsElement()); > > this._levelNavigationBar = new WI.NavigationBar(document.createElement("nav")); > this.headerView.addSubview(this._levelNavigationBar); >@@ -69,6 +116,8 @@ WI.AuditTestGroupContentView = class AuditTestGroupContentView extends WI.AuditT > a.append(b); > return a; > }); >+ >+ this._updateClassList(); > } > > layout() >@@ -78,6 +127,18 @@ WI.AuditTestGroupContentView = class AuditTestGroupContentView extends WI.AuditT > > super.layout(); > >+ if (WI.auditManager.editing) { >+ if (this._levelScopeBar) { >+ this._levelNavigationBar.removeNavigationItem(this._levelScopeBar); >+ this._levelScopeBar = null; >+ } >+ >+ this._percentageContainer.hidden = true; >+ >+ this.resetFilter(); >+ return; >+ } >+ > let result = this.representedObject.result; > if (!result) { > if (this._levelScopeBar) { >@@ -148,24 +209,34 @@ WI.AuditTestGroupContentView = class AuditTestGroupContentView extends WI.AuditT > if (this.representedObject instanceof WI.AuditTestGroup) { > this.representedObject.addEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this); > this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this); >+ >+ if (this.representedObject.editable) { >+ this.representedObject.addEventListener(WI.AuditTestGroup.Event.TestAdded, this._handleTestGroupTestAdded, this); >+ this.representedObject.addEventListener(WI.AuditTestGroup.Event.TestRemoved, this._handleTestGroupTestRemoved, this); >+ } > } > >- for (let subobject of this._subobjects()) { >- if (subobject instanceof WI.AuditTestBase && subobject.disabled) >- continue; >- >- let view = WI.ContentView.contentViewForRepresentedObject(subobject); >- this.contentView.addSubview(view); >- view.shown(); >- } >+ console.assert(!this._viewForSubobject.size); >+ for (let subobject of this._subobjects()) >+ this._addTest(subobject); > } > > hidden() > { >- for (let view of this.contentView.subviews) >- view.hidden(); >+ if (this.representedObject instanceof WI.AuditTestGroup) { >+ this.representedObject.removeEventListener(WI.AuditTestBase.Event.Progress, this._handleTestGroupProgress, this); >+ this.representedObject.removeEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this); > >+ if (this.representedObject.editable) { >+ this.representedObject.removeEventListener(WI.AuditTestGroup.Event.TestAdded, this._handleTestGroupTestAdded, this); >+ this.representedObject.removeEventListener(WI.AuditTestGroup.Event.TestRemoved, this._handleTestGroupTestRemoved, this); >+ } >+ } >+ >+ for (let view of this._viewForSubobject.values()) >+ view.hidden(); > this.contentView.removeAllSubviews(); >+ this._viewForSubobject.clear(); > > super.hidden(); > } >@@ -196,6 +267,15 @@ WI.AuditTestGroupContentView = class AuditTestGroupContentView extends WI.AuditT > this.placeholderElement.__progress = document.createElement("progress"); > this.placeholderElement.__progress.value = 0; > this.placeholderElement.appendChild(this.placeholderElement.__progress); >+ >+ let stopAuditNavigationItem = new WI.ButtonNavigationItem("stop-audit", WI.UIString("Stop"), "Images/AuditStop.svg", 13, 13); >+ stopAuditNavigationItem.buttonStyle = WI.ButtonNavigationItem.Style.ImageAndText; >+ stopAuditNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, (event) => { >+ WI.auditManager.stop(); >+ }, WI.auditManager); >+ >+ let stopAuditHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to stop running."), stopAuditNavigationItem); >+ this.placeholderElement.appendChild(stopAuditHelpElement); > } > > super.showRunningPlaceholder(); >@@ -215,6 +295,14 @@ WI.AuditTestGroupContentView = class AuditTestGroupContentView extends WI.AuditT > return []; > } > >+ _updateClassList() >+ { >+ let subobjects = this._subobjects(); >+ let containsTestGroup = subobjects.some((test) => test instanceof WI.AuditTestGroup || test instanceof WI.AuditTestGroupResult); >+ this.element.classList.toggle("contains-test-group", containsTestGroup); >+ this.element.classList.toggle("contains-test-case", !containsTestGroup && subobjects.some((test) => test instanceof WI.AuditTestCase || test instanceof WI.AuditTestCaseResult)); >+ } >+ > _updateLevelScopeBar(levels) > { > if (!this._levelScopeBar) >@@ -223,12 +311,23 @@ WI.AuditTestGroupContentView = class AuditTestGroupContentView extends WI.AuditT > for (let item of this._levelScopeBar.items) > item.selected = levels.includes(item.id); > >- for (let view of this.contentView.subviews) { >+ for (let view of this._viewForSubobject.values()) { > if (view instanceof WI.AuditTestGroupContentView) > view._updateLevelScopeBar(levels); > } > } > >+ _addTest(test) >+ { >+ console.assert(!this._viewForSubobject.has(test)); >+ >+ let view = WI.ContentView.contentViewForRepresentedObject(test); >+ this.contentView.addSubview(view); >+ view.shown(); >+ >+ this._viewForSubobject.set(test, view); >+ } >+ > _handleTestGroupProgress(event) > { > let {index, count} = event.data; >@@ -242,6 +341,32 @@ WI.AuditTestGroupContentView = class AuditTestGroupContentView extends WI.AuditT > this.placeholderElement.__progress.value = 0; > } > >+ _handleTestGroupTestAdded(event) >+ { >+ console.assert(WI.auditManager.editing); >+ >+ let {test} = event.data; >+ >+ this._addTest(test); >+ >+ this._updateClassList(); >+ } >+ >+ _handleTestGroupTestRemoved(event) >+ { >+ console.assert(WI.auditManager.editing); >+ >+ let {test} = event.data; >+ >+ let view = this._viewForSubobject.get(test); >+ console.assert(view); >+ >+ view.hidden(); >+ this.contentView.removeSubview(view); >+ >+ this._updateClassList(); >+ } >+ > _handleLevelScopeBarSelectionChanged(event) > { > this.needsLayout(); >diff --git a/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.css b/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.css >index e9d186cfd42c6dbece992b769602922ea89fca81..2f373b94d0d2c7ab1a1f3ddbb79006b29b0eca75 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.css >+++ b/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.css >@@ -35,20 +35,26 @@ > height: 100%; > } > >-.tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active) > .status:hover > img { >+.tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img { > width: 75%; > height: 75%; > margin: 0 auto; > content: url(../Images/AuditStart.svg) !important; > } > >-.tree-outline .item.audit > .status:not(:hover) > img.show-on-hover, >-.tree-outline .item.audit.test-group.expanded:not(.unsupported, .editing-audits) > .status:not(:hover) { >+body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active).selected:hover > .status > img, >+body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit.test-case.selected > .status > .indeterminate-progress-spinner, >+body:not(.window-inactive, .window-docked-inactive) .tree-outline:focus-within .item.audit.test-group.selected > .status > progress { >+ filter: var(--filter-invert); >+} >+ >+.tree-outline .item.audit:not(:hover) > .status > img.show-on-hover, >+.tree-outline .item.audit.test-group.expanded:not(.unsupported, .editing-audits):not(:hover) > .status { > opacity: 0; > } > > .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.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 { > display: none; >@@ -63,6 +69,7 @@ > height: 75%; > margin: 0 auto; > content: url(../Images/Warning.svg); >+ pointer-events: none; > } > > .tree-outline .item.audit > .status > img.pass { >@@ -102,6 +109,10 @@ > } > > @media (prefers-color-scheme: dark) { >+ .tree-outline .item.audit:matches(.test-case, .test-group):not(.unsupported, .manager-active):hover > .status > img { >+ filter: var(--filter-invert) brightness(90%); >+ } >+ > .audit.test-case .icon { > content: url(../Images/TypeIcons.svg#AuditTestCase-dark); > } >diff --git a/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js b/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js >index 6502d4233d4d60505c6979bdb50b6e68518c19b4..9d060fd88d5b16535a36a188289a930a95aba17d 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js >+++ b/Source/WebInspectorUI/UserInterface/Views/AuditTreeElement.js >@@ -51,7 +51,14 @@ WI.AuditTreeElement = class AuditTreeElement extends WI.GeneralTreeElement > super(classNames, representedObject.name, subtitle, representedObject, options); > > if (isTestGroup) >- this._expandedSetting = new WI.Setting(`audit-tree-element-${this.representedObject.name}-expanded`, false); >+ this._expandedSetting = new WI.Setting(WI.AuditTreeElement.expandedSettingKey(this.representedObject.name), false); >+ } >+ >+ // Static >+ >+ static expandedSettingKey(name) >+ { >+ return `audit-tree-element-${name}-expanded`; > } > > // Protected >@@ -69,6 +76,14 @@ WI.AuditTreeElement = class AuditTreeElement extends WI.GeneralTreeElement > else if (this.representedObject instanceof WI.AuditTestGroup) > this.representedObject.addEventListener(WI.AuditTestBase.Event.Scheduled, this._handleTestGroupScheduled, this); > >+ if (this.representedObject.editable) { >+ this.representedObject.addEventListener(WI.AuditTestBase.Event.NameChanged, this._handleTestNameChanged, this); >+ this.representedObject.addEventListener(WI.AuditTestBase.Event.SupportedChanged, this._handleTestSupportedChanged, this); >+ >+ if (this.representedObject instanceof WI.AuditTestGroup) >+ this.representedObject.addEventListener(WI.AuditTestGroup.Event.TestAdded, this._handleTestGroupTestAdded, this); >+ } >+ > WI.auditManager.addEventListener(WI.AuditManager.Event.EditingChanged, this._handleManagerEditingChanged, this); > WI.auditManager.addEventListener(WI.AuditManager.Event.TestScheduled, this._handleAuditManagerTestScheduled, this); > WI.auditManager.addEventListener(WI.AuditManager.Event.TestCompleted, this._handleAuditManagerTestCompleted, this); >@@ -129,37 +144,58 @@ WI.AuditTreeElement = class AuditTreeElement extends WI.GeneralTreeElement > if (!(this.representedObject instanceof WI.AuditTestBase)) > return false; > >- if (!(this.parent instanceof WI.TreeOutline)) >- return false; >- > if (!WI.auditManager.editing) > return false; > >- WI.auditManager.removeTest(this.representedObject); >+ this.representedObject.remove(); > > return true; > } > >+ canSelectOnMouseDown(event) >+ { >+ if (this.representedObject instanceof WI.AuditTestBase && this.representedObject.supported && this.status.contains(event.target)) >+ return false; >+ >+ return super.canSelectOnMouseDown(event); >+ } >+ > populateContextMenu(contextMenu, event) > { >- if (WI.auditManager.runningState === WI.AuditManager.RunningState.Inactive) { >- contextMenu.appendItem(WI.UIString("Start"), (event) => { >- this._start(); >- }); >- } >+ let isTest = this.representedObject instanceof WI.AuditTestBase; >+ let editing = WI.auditManager.editing; > > contextMenu.appendSeparator(); > >- if (this.representedObject instanceof WI.AuditTestBase) { >- contextMenu.appendItem(WI.UIString("Export Test"), (event) => { >- WI.auditManager.export(this.representedObject); >- }); >- } >+ if (editing) { >+ if (isTest) { >+ contextMenu.appendItem(WI.UIString("Duplicate Audit"), async () => { >+ let audit = await this.representedObject.clone(); >+ WI.auditManager.addTest(audit); >+ }); > >- if (this.representedObject.result) { >- contextMenu.appendItem(WI.UIString("Export Result"), (event) => { >+ contextMenu.appendSeparator(); >+ >+ contextMenu.appendItem(WI.UIString("Delete Audit"), () => { >+ this.representedObject.remove(); >+ }, !this.representedObject.editable && this.representedObject.disabled); >+ } >+ } else { >+ if (isTest) { >+ contextMenu.appendItem(WI.UIString("Start Audit"), () => { >+ this._start(); >+ }, WI.auditManager.runningState !== WI.AuditManager.RunningState.Inactive); >+ >+ contextMenu.appendSeparator(); >+ >+ contextMenu.appendItem(WI.UIString("Export Audit"), () => { >+ WI.auditManager.export(this.representedObject); >+ }); >+ } >+ >+ contextMenu.appendItem(WI.UIString("Export Result"), () => { > WI.auditManager.export(this.representedObject.result); >- }); >+ }, !this.representedObject.result); > } > > contextMenu.appendSeparator(); >@@ -315,6 +351,26 @@ WI.AuditTreeElement = class AuditTreeElement extends WI.GeneralTreeElement > this._showRunningProgress(); > } > >+ _handleTestNameChanged(event) >+ { >+ this.mainTitle = this.representedObject.name; >+ >+ if (this.representedObject instanceof WI.AuditTestGroup) >+ this._expandedSetting = new WI.Setting(WI.AuditTreeElement.expandedSettingKey(this.representedObject.name), !!WI.Setting.migrateValue(WI.AuditTreeElement.expandedSettingKey(event.data.oldName))); >+ } >+ >+ _handleTestSupportedChanged(event) >+ { >+ this._updateStatus(); >+ } >+ >+ _handleTestGroupTestAdded(event) >+ { >+ let {test} = event.data; >+ >+ this.appendChild(new WI.AuditTreeElement(test)); >+ } >+ > _handleManagerEditingChanged(event) > { > this._updateStatus(); >diff --git a/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.css b/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.css >index 6551105e6bbc02b5eff8e0eff8406cd5ea82a6c8..aaf3a036c5ecd646db1c10633c03ab993f61f421 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.css >+++ b/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.css >@@ -201,14 +201,10 @@ > .navigation-bar > .item.canvas-recording-auto-capture > label > input { > width: 1.5em; > min-width: 1.5em; >- margin: 0 var(--recording-auto-capture-input-margin); >+ margin: 0 4px; > text-align: center; > } > >-.navigation-bar > .item.canvas-recording-auto-capture > label > input::-webkit-inner-spin-button { >- -webkit-appearance: none; >-} >- > @media (prefers-color-scheme: dark) { > .content-view.canvas-overview > .content-view.canvas, > .content-view.canvas-overview > .content-view.canvas > .preview > img { >diff --git a/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.js b/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.js >index 0d165dd268d437028a43d149e07cfa838b4de7ab..332acadd6c9a532e86cfaf55988efbde027e6cf9 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/CanvasOverviewContentView.js >@@ -49,7 +49,6 @@ WI.CanvasOverviewContentView = class CanvasOverviewContentView extends WI.Collec > this._recordingAutoCaptureFrameCountInputElement = document.createElement("input"); > this._recordingAutoCaptureFrameCountInputElement.type = "number"; > this._recordingAutoCaptureFrameCountInputElement.min = 0; >- this._recordingAutoCaptureFrameCountInputElement.style.setProperty("--recording-auto-capture-input-margin", CanvasOverviewContentView.recordingAutoCaptureInputMargin + "px"); > this._recordingAutoCaptureFrameCountInputElement.addEventListener("input", this._handleRecordingAutoCaptureInput.bind(this)); > this._recordingAutoCaptureFrameCountInputElementValue = WI.settings.canvasRecordingAutoCaptureFrameCount.value; > >@@ -69,10 +68,6 @@ WI.CanvasOverviewContentView = class CanvasOverviewContentView extends WI.Collec > this._savedRecordingsTreeOutline = null; > } > >- // Static >- >- static get recordingAutoCaptureInputMargin() { return 4; } >- > // Public > > get navigationItems() >@@ -226,16 +221,7 @@ WI.CanvasOverviewContentView = class CanvasOverviewContentView extends WI.Collec > this._recordingAutoCaptureFrameCountInputElementValue = frameCount; > } > >- WI.ImageUtilities.scratchCanvasContext2D((context) => { >- if (!this._recordingAutoCaptureFrameCountInputElement.__cachedFont) { >- let computedStyle = window.getComputedStyle(this._recordingAutoCaptureFrameCountInputElement); >- this._recordingAutoCaptureFrameCountInputElement.__cachedFont = computedStyle.font; >- } >- >- context.font = this._recordingAutoCaptureFrameCountInputElement.__cachedFont; >- let textMetrics = context.measureText(this._recordingAutoCaptureFrameCountInputElement.value || this._recordingAutoCaptureFrameCountInputElement.placeholder); >- this._recordingAutoCaptureFrameCountInputElement.style.setProperty("width", (textMetrics.width + (2 * CanvasOverviewContentView.recordingAutoCaptureInputMargin)) + "px"); >- }); >+ this._recordingAutoCaptureFrameCountInputElement.autosize(); > > return frameCount; > } >diff --git a/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.css b/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.css >new file mode 100644 >index 0000000000000000000000000000000000000000..84253a598a023290a815209cd18d667d10f09bee >--- /dev/null >+++ b/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.css >@@ -0,0 +1,34 @@ >+/* >+ * Copyright (C) 2020 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' >+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, >+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS >+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR >+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF >+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS >+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN >+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) >+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF >+ * THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+.popover .create-audit-content { >+ padding: 5px; >+ margin: 2px; >+} >+ >+.popover .create-audit-content > .editor-wrapper { >+ display: flex; >+ margin-top: 4px; >+} >diff --git a/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.js b/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.js >new file mode 100644 >index 0000000000000000000000000000000000000000..bb0c4f36077e16660039d4aaeb15353d41a8f94c >--- /dev/null >+++ b/Source/WebInspectorUI/UserInterface/Views/CreateAuditPopover.js >@@ -0,0 +1,122 @@ >+/* >+ * Copyright (C) 2020 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' >+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, >+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS >+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR >+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF >+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS >+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN >+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) >+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF >+ * THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+WI.CreateAuditPopover = class CreateAuditPopover extends WI.Popover >+{ >+ constructor(delegate) >+ { >+ super(delegate); >+ >+ this._audit = null; >+ >+ this._targetElement = null; >+ this._preferredEdges = null; >+ >+ this.windowResizeHandler = this._presentOverTargetElement.bind(this); >+ } >+ >+ // Public >+ >+ get audit() { return this._audit; } >+ >+ show(targetElement, preferredEdges) >+ { >+ this._targetElement = targetElement; >+ this._preferredEdges = preferredEdges; >+ >+ let contentElement = document.createElement("div"); >+ contentElement.classList.add("create-audit-content"); >+ >+ let label = contentElement.appendChild(document.createElement("div")); >+ label.classList.add("label"); >+ label.textContent = WI.UIString("Create audit:"); >+ >+ let editorWrapper = contentElement.appendChild(document.createElement("div")); >+ editorWrapper.classList.add("editor-wrapper"); >+ >+ this._typeSelectElement = editorWrapper.appendChild(document.createElement("select")); >+ >+ let createOption = (text, value) => { >+ let optionElement = this._typeSelectElement.appendChild(document.createElement("option")); >+ optionElement.textContent = text; >+ optionElement.value = value; >+ return optionElement; >+ }; >+ >+ createOption(WI.UIString("Test Case"), "test-case"); >+ createOption(WI.UIString("Group"), "group"); >+ >+ this._nameInputElement = editorWrapper.appendChild(document.createElement("input")); >+ this._nameInputElement.placeholder = WI.UIString("Name"); >+ this._nameInputElement.addEventListener("keydown", (event) => { >+ if (event.keyCode === WI.KeyboardShortcut.Key.Enter.keyCode || event.keyCode === WI.KeyboardShortcut.Key.Escape.keyCode) { >+ event.stop(); >+ this.dismiss(); >+ } >+ }); >+ >+ this.content = contentElement; >+ >+ this._presentOverTargetElement(); >+ } >+ >+ dismiss() >+ { >+ const testFunction = function() { >+ let result = { >+ level: "pass", >+ }; >+ return result; >+ }; >+ const testFunctionString = WI.AuditTestCase.stringifyFunction(testFunction, 8); >+ >+ let type = this._typeSelectElement.value; >+ let name = this._nameInputElement.value; >+ if (type && name) { >+ switch (type) { >+ case "test-case": >+ this._audit = new WI.AuditTestCase(name, testFunctionString); >+ break; >+ >+ case "group": >+ this._audit = new WI.AuditTestGroup(name, [new WI.AuditTestCase(WI.unlocalizedString("test-case"), testFunctionString)]); >+ break; >+ } >+ } >+ >+ super.dismiss(); >+ } >+ >+ // Private >+ >+ _presentOverTargetElement() >+ { >+ if (!this._targetElement) >+ return; >+ >+ let targetFrame = WI.Rect.rectFromClientRect(this._targetElement.getBoundingClientRect()); >+ this.present(targetFrame.pad(2), this._preferredEdges); >+ } >+}; >diff --git a/Source/WebInspectorUI/UserInterface/Views/Main.css b/Source/WebInspectorUI/UserInterface/Views/Main.css >index c08be3bb5fa2bd776e4c68b9f7ca3c6d90eed472..96732bc4f52e674aef3e834e68cbfff08aa9aca8 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/Main.css >+++ b/Source/WebInspectorUI/UserInterface/Views/Main.css >@@ -227,12 +227,15 @@ body.docked:matches(.right, .left) #navigation-sidebar.collapsed > .resizer { > display: block; > } > >+.navigation-item-help:not(:first-child) { >+ margin-top: 8px; >+} >+ > .navigation-item-help > .navigation-bar { > display: inline-flex; > height: 20px; >- padding: 0 4px; > border-bottom: none; >- vertical-align: -3px; >+ vertical-align: var(--navigation-item-help-navigation-bar-vertical-align, sub); > } > > .navigation-item-help > .navigation-bar > .item.button { >diff --git a/Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js b/Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js >index d95acbe930f58f0388411e4f9fc8fe8cc32cbc83..4b9d37b5f1fdb83549bb80bb7a6bfaf6efb555e4 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js >+++ b/Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js >@@ -69,7 +69,7 @@ WI.SearchSidebarPanel = class SearchSidebarPanel extends WI.NavigationSidebarPan > let searchNavigationItem = new WI.ButtonNavigationItem("search", WI.UIString("Search Resource Content"), "Images/Search.svg", 15, 15); > searchNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleDefaultContentViewSearchNavigationItemClicked, this); > >- let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to see recent searches"), searchNavigationItem); >+ let importHelpElement = WI.createNavigationItemHelp(WI.UIString("Press %s to see recent searches."), searchNavigationItem); > contentPlaceholder.appendChild(importHelpElement); > > this.contentBrowser.showContentView(contentView); >diff --git a/Source/WebInspectorUI/UserInterface/Views/Variables.css b/Source/WebInspectorUI/UserInterface/Views/Variables.css >index 155d89ab90e81d3c2be7342d8342a806ec339ebe..93d3f7d9de0b9c61b21aa23de79c3b27ea04be0b 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/Variables.css >+++ b/Source/WebInspectorUI/UserInterface/Views/Variables.css >@@ -190,6 +190,9 @@ > > --focus-ring-outline-offset: -2px; > >+ /* Invert colors yet preserve the hue */ >+ --filter-invert: invert(100%) hue-rotate(180deg); >+ > --undocked-title-area-height: 0px; > --tab-bar-height: var(--navigation-bar-height); > --navigation-bar-height: 29px; >@@ -362,9 +365,6 @@ body.mac-platform.big-sur:not(.docked) { > > --timeline-scanner-color: hsl(0, 0%, 80%); > >- /* Invert colors yet preserve the hue */ >- --filter-invert: invert(100%) hue-rotate(180deg); >- > /* TODO: Use the same variable for the default theme */ > --overlay-background: hsla(0, 0%, 24%, 0.9); >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 215555
:
406693
|
406694
|
406695
|
406769
|
407168
|
407169
|
407170
|
407171
|
407174
|
407278
|
407514