chrome-devtools-frontend 1.0.1717196 → 1.0.1717709
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/devtools-testing-guidance/SKILL.md +4 -2
- package/.agents/skills/ui-eng-vision-local-lit-renderer/SKILL.md +57 -9
- package/.agents/skills/ui-eng-vision-logic-consolidator/SKILL.md +27 -3
- package/.agents/skills/ui-eng-vision-orchestrator/SKILL.md +32 -2
- package/.agents/skills/ui-eng-vision-test-scaffolder/SKILL.md +37 -7
- package/.agents/skills/ui-eng-vision-widget-promoter/SKILL.md +4 -4
- package/.agents/skills/ui-widgets/SKILL.md +8 -8
- package/docs/ui_engineering.md +34 -0
- package/front_end/core/common/Settings.ts +5 -0
- package/front_end/core/host/UserMetricsEnums.ts +1 -0
- package/front_end/core/sdk/CSSModel.ts +21 -0
- package/front_end/core/sdk/CSSPropertyParser.ts +11 -2
- package/front_end/core/sdk/DOMModel.ts +1 -1
- package/front_end/core/sdk/DebuggerModel.ts +3 -0
- package/front_end/core/sdk/EmulationModel.ts +40 -39
- package/front_end/core/sdk/NetworkManager.ts +1 -0
- package/front_end/core/sdk/NetworkRequest.ts +21 -0
- package/front_end/core/sdk/RuntimeModel.ts +8 -3
- package/front_end/devtools_compatibility.js +1 -0
- package/front_end/generated/Deprecation.ts +0 -7
- package/front_end/generated/InspectorBackendCommands.ts +1 -1
- package/front_end/generated/protocol.ts +1 -0
- package/front_end/models/ai_assistance/AiAgent2.ts +11 -5
- package/front_end/models/ai_assistance/AiConversation.ts +3 -2
- package/front_end/models/ai_assistance/AiUtils.ts +5 -2
- package/front_end/models/ai_assistance/agents/AccessibilityAgent.ts +9 -4
- package/front_end/models/ai_assistance/agents/AiAgent.ts +247 -121
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +9 -5
- package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +2 -9
- package/front_end/models/ai_assistance/tools/RunLighthouse.ts +6 -3
- package/front_end/models/ai_assistance/tools/Tool.ts +5 -1
- package/front_end/models/bindings/CompilerScriptMapping.ts +40 -8
- package/front_end/models/bindings/ResourceScriptMapping.ts +1 -1
- package/front_end/models/comment_manager/CommentThread.ts +13 -0
- package/front_end/models/computed_style/ComputedStyleModel.ts +1 -0
- package/front_end/models/issues_manager/GenericIssue.ts +18 -0
- package/front_end/models/issues_manager/descriptions/genericGeolocationPromptWithoutUserGesture.md +5 -0
- package/front_end/models/javascript_metadata/NativeFunctions.ts +0 -4
- package/front_end/models/lighthouse/RunTypes.ts +16 -0
- package/front_end/models/stack_trace/StackTraceModel.ts +24 -3
- package/front_end/models/trace/extras/TraceTree.ts +2 -7
- package/front_end/models/trace/helpers/Timing.ts +11 -22
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +23 -2
- package/front_end/panels/ai_assistance/README.md +3 -1
- package/front_end/panels/application/AppManifestView.ts +1 -4
- package/front_end/panels/comments/CommentsPane.ts +7 -3
- package/front_end/panels/comments/comments-meta.ts +1 -1
- package/front_end/panels/common/CommentThreadWidget.ts +67 -25
- package/front_end/panels/common/CommentsOverlayWidget.ts +13 -0
- package/front_end/panels/elements/PropertyRenderer.ts +103 -10
- package/front_end/panels/elements/StylePropertyTreeElement.ts +107 -18
- package/front_end/panels/elements/stylePropertiesTreeOutline.css +6 -1
- package/front_end/panels/emulation/deviceModeView.css +3 -3
- package/front_end/panels/lighthouse/LighthouseController.ts +28 -21
- package/front_end/panels/lighthouse/LighthousePanel.ts +5 -6
- package/front_end/panels/lighthouse/LighthouseProtocolService.ts +24 -16
- package/front_end/panels/screencast/ScreencastApp.ts +2 -2
- package/front_end/panels/screencast/ScreencastView.ts +76 -45
- package/front_end/panels/sources/BreakpointEditDialog.ts +3 -6
- package/front_end/panels/timeline/CountersGraph.ts +1 -1
- package/front_end/panels/timeline/TimelineFlameChartView.ts +2 -9
- package/front_end/panels/timeline/utils/EntryNodes.ts +1 -1
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +42 -12
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +154 -26
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/HTTPRequest.d.ts +47 -7
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/HTTPRequest.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/HTTPRequest.js +56 -4
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/HTTPRequest.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Page.d.ts +6 -4
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Page.js +5 -2
- package/front_end/third_party/puppeteer/package/lib/puppeteer/api/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/HTTPRequest.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/HTTPRequest.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/HTTPRequest.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Page.d.ts +2 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Page.js +23 -11
- package/front_end/third_party/puppeteer/package/lib/puppeteer/bidi/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/HTTPRequest.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/HTTPRequest.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/HTTPRequest.js +1 -2
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/HTTPRequest.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/NetworkManager.d.ts +13 -7
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/NetworkManager.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/NetworkManager.js +26 -11
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/NetworkManager.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Page.d.ts +2 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/TargetManager.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/TargetManager.js +69 -13
- package/front_end/third_party/puppeteer/package/lib/puppeteer/cdp/TargetManager.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/ConnectOptions.d.ts +10 -0
- package/front_end/third_party/puppeteer/package/lib/puppeteer/common/ConnectOptions.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/types.d.ts +42 -12
- package/front_end/third_party/puppeteer/package/package.json +4 -4
- package/front_end/third_party/puppeteer/package/src/api/HTTPRequest.ts +103 -11
- package/front_end/third_party/puppeteer/package/src/api/Page.ts +11 -6
- package/front_end/third_party/puppeteer/package/src/bidi/HTTPRequest.ts +4 -4
- package/front_end/third_party/puppeteer/package/src/bidi/Page.ts +26 -14
- package/front_end/third_party/puppeteer/package/src/cdp/HTTPRequest.ts +7 -7
- package/front_end/third_party/puppeteer/package/src/cdp/NetworkManager.ts +40 -18
- package/front_end/third_party/puppeteer/package/src/cdp/Page.ts +2 -3
- package/front_end/third_party/puppeteer/package/src/cdp/TargetManager.ts +87 -14
- package/front_end/third_party/puppeteer/package/src/common/ConnectOptions.ts +10 -0
- package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
- package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
- package/front_end/ui/legacy/SplitWidget.ts +9 -8
- package/front_end/ui/visual_logging/KnownContextValues.ts +42 -0
- package/package.json +1 -1
- package/.agents/skills/devtools-ux-writing-refactor/SKILL.md +0 -142
|
@@ -37,7 +37,8 @@ Use API tests for integration tests that verify business logic requiring more co
|
|
|
37
37
|
- Tests that require extensive CDP mock setup.
|
|
38
38
|
- Verifying the "foundational layer" of DevTools models against a real browser/web page.
|
|
39
39
|
- **Location:** Defined in `front_end/` with `.test.api.ts` extension (e.g. `Universe.test.api.ts`).
|
|
40
|
-
- **
|
|
40
|
+
- **Test functions:** import `describe`, `it` and `setup` from `test/api/mocha-interface.ts`. They are not available as globals.
|
|
41
|
+
- **BUILD.gn**: test file should be added to `devtools_api_test_module("api_tests")` target in the corresponding `BUILD.gn` file, which needs `test/api` in its `ts_deps`.
|
|
41
42
|
|
|
42
43
|
---
|
|
43
44
|
|
|
@@ -49,4 +50,5 @@ Use E2E tests to verify real end-to-end user stories and user journeys.
|
|
|
49
50
|
- Verifying full user journeys across DevTools panels and drawers.
|
|
50
51
|
- Complex UI behaviors that require extensive user interaction setup and benefit from a complete DevTools frontend page connected to an inspected target page over CDP.
|
|
51
52
|
- **Location:** Defined in `test/e2e/` (e.g. `test/e2e/console/console-log.test.ts`).
|
|
52
|
-
- **
|
|
53
|
+
- **Test functions:** import `describe`, `it` and `setup` from `test/e2e/conductor/mocha-interface.ts` (e.g. `import {describe, it} from '../conductor/mocha-interface.js';`). They are not available as globals.
|
|
54
|
+
- **BUILD.gn**: test file should be added to the `node_ts_library` target in the corresponding `BUILD.gn` file, which needs `"../conductor:implementation"` in its `ts_deps`. And `node_ts_library` in `test/e2e/BUILD.gn` for its dependencies.
|
|
@@ -34,14 +34,18 @@ existing element containers.
|
|
|
34
34
|
that method. **Do not** introduce Widget lifecycle methods
|
|
35
35
|
(`performUpdate`) on classes that do not inherit them.
|
|
36
36
|
|
|
37
|
-
3. **Import Modern Templating**:
|
|
37
|
+
3. **Import Modern Templating (Named Imports Only)**:
|
|
38
38
|
|
|
39
|
-
*
|
|
39
|
+
* Always use named imports from `../../ui/lit/lit.js`:
|
|
40
40
|
|
|
41
41
|
```typescript
|
|
42
|
-
import {html, render,
|
|
42
|
+
import {html, nothing, render, Directives, type LitTemplate} from '../../ui/lit/lit.js';
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
* **NEVER** use namespace imports like `import * as Lit` or reference
|
|
46
|
+
`Lit.nothing` / `Lit.render`. Named imports enable better tree-shaking and
|
|
47
|
+
maintain consistent codebase style.
|
|
48
|
+
|
|
45
49
|
4. **Modern Component Mapping**:
|
|
46
50
|
* **MANDATORY**: You **MUST** read and load [ui_engineering.md](../../../docs/ui_engineering.md) first to understand specific component mappings (e.g., Toolbar vs `devtools-toolbar`). Do not assume standard Lit defaults.
|
|
47
51
|
* If unsure, also consult the [automatic migration code](../../../scripts/eslint_rules/lib/no-imperative-dom-api.ts)
|
|
@@ -115,13 +119,53 @@ existing element containers.
|
|
|
115
119
|
* The Gerrit Code Review UI cannot identify moved blocks of unchanged code.
|
|
116
120
|
* If blocks of code need to be reordered, create a "prefactoring" change that first extracts the code that needs to be reordered into a named helper functions and pause the migration, waiting user confirmation.
|
|
117
121
|
|
|
118
|
-
12. **
|
|
122
|
+
12. **Embedding Pre-Existing `UI.Widget` Instances via `UI.Widget.WrapperWidget`**:
|
|
123
|
+
|
|
124
|
+
* Use `${widget(UI.Widget.WrapperWidget, {widget: input.childWidget})}` when
|
|
125
|
+
embedding widgets owned by `UI.ViewManager`
|
|
126
|
+
(`TabbedViewLocation.tabbedPane()`, `StackLocation.widget()`) or when
|
|
127
|
+
conditionally moving a shared widget instance between containers
|
|
128
|
+
(`{widget: shouldShow ? this.#sharedWidget : null}`).
|
|
129
|
+
* `WrapperWidget` automatically handles showing and detaching the inner
|
|
130
|
+
widget and forwards calculateConstraints without requiring custom wrapper
|
|
131
|
+
classes.
|
|
132
|
+
|
|
133
|
+
13. **Declarative `<devtools-split-view>` Guidelines**:
|
|
134
|
+
|
|
135
|
+
* **CSS Sizing**: Ensure `<devtools-split-view>` has `flex: auto; height: 100%;`
|
|
136
|
+
in CSS so it fills available container space and doesn't collapse to 0x0.
|
|
137
|
+
* **Reuse Existing Settings for `showMode`**: When binding `show=${...}`
|
|
138
|
+
declaratively on `<devtools-split-view name="...">`, reuse the existing
|
|
139
|
+
`SplitWidget` setting (`{vertical?: {size, showMode}, horizontal?: {size, showMode}}`)
|
|
140
|
+
for `showMode` rather than creating new boolean settings (which loses user
|
|
141
|
+
preferences and requires `KnownContextValues.ts` updates).
|
|
142
|
+
* **Keep Environment Overrides in Templates**: Keep environment overrides
|
|
143
|
+
(like `!input.isTraceApp`) in the `DEFAULT_VIEW` template (e.g.,
|
|
144
|
+
`show=${!input.isTraceApp && input.isSidebarOpen ? 'both' : 'main'}`)
|
|
145
|
+
rather than baking them into the presenter's setting state.
|
|
146
|
+
* **Clean Up Resizers**: Do not leave unused `resizer-widget` classes in
|
|
147
|
+
templates; `<devtools-split-view>` manages its own built-in splitter
|
|
148
|
+
unless custom elements are explicitly registered with `installResizer`.
|
|
149
|
+
|
|
150
|
+
14. **Declarative Long-Press Action Buttons (`bindToAction`)**:
|
|
151
|
+
|
|
152
|
+
* Legacy `UI.Toolbar.Toolbar.createLongPressActionButton(action, [subButtons])`
|
|
153
|
+
maps declaratively to nesting option `<devtools-button>` elements directly
|
|
154
|
+
inside `<devtools-button ${bindToAction('action-id')}>...</devtools-button>`.
|
|
155
|
+
|
|
156
|
+
15. **Audit Removed `UIStrings`**:
|
|
157
|
+
|
|
158
|
+
* If a `UIStrings` entry becomes unused during template migration, verify
|
|
159
|
+
that a conditional UI branch (such as a dynamically shown checkbox, button,
|
|
160
|
+
or tooltip) wasn't accidentally dropped before deleting the string.
|
|
161
|
+
|
|
162
|
+
16. **Micro-Commit Strategy**:
|
|
119
163
|
|
|
120
164
|
* Do not attempt to convert every helper method and UI component in a single monolithic commit. Break the template extraction into **small, self-contained micro-commits** (e.g., migrating one helper method, toolbar button, or list item render at a time).
|
|
121
165
|
* Verify with build and run tests after each micro-commit to guarantee zero regressions and 100% visual parity before moving to the next compartment.
|
|
122
166
|
* Provide the exact list of files and commit message when presenting progress so the change history remains atomic and easy to review.
|
|
123
167
|
|
|
124
|
-
|
|
168
|
+
17. **Wait for confirmation**:
|
|
125
169
|
|
|
126
170
|
* Wait for an explicit confirmation from the user (or Parent Orchestrator Agent) before proceeding to the next step.
|
|
127
171
|
|
|
@@ -130,8 +174,12 @@ existing element containers.
|
|
|
130
174
|
## 🔍 Mental Audit (Internal Self-Correction)
|
|
131
175
|
|
|
132
176
|
*Before reporting back or committing, re-read the instructions and verify:*
|
|
133
|
-
1. ❓ **
|
|
134
|
-
2. ❓ **
|
|
135
|
-
3. ❓ **
|
|
136
|
-
4. ❓ **
|
|
177
|
+
1. ❓ **Named Imports**: Did I use named imports `{html, nothing, render}` from `ui/lit/lit.js` (and no `import * as Lit`)?
|
|
178
|
+
2. ❓ **Tag Parity**: Did I accidentally change any `span` to `div` (or vice versa)?
|
|
179
|
+
3. ❓ **Attribute Drop**: Did I drop any `aria-*`, `role`, or custom data attributes?
|
|
180
|
+
4. ❓ **Clang-Off Boundary**: Is the template wrapped in `// clang-format off`?
|
|
181
|
+
5. ❓ **Split Views**: Does `<devtools-split-view>` have `flex: auto; height: 100%;` CSS and reuse existing split settings?
|
|
182
|
+
6. ❓ **String Audit**: Did I verify that any removed `UIStrings` weren't from accidentally dropped UI branches?
|
|
183
|
+
7. ❓ **Build**: Does the code compile and do visual tests pass with 0% diff?
|
|
184
|
+
|
|
137
185
|
|
|
@@ -76,7 +76,29 @@ consolidating them into private helper methods with explicit data contracts.
|
|
|
76
76
|
that first extracts the code that needs to be reordered into named
|
|
77
77
|
helper functions and pause the migration, waiting for user confirmation.
|
|
78
78
|
|
|
79
|
-
6. **
|
|
79
|
+
6. **No Write-Only State Fields in Prefactor CLs**:
|
|
80
|
+
|
|
81
|
+
* Do not introduce `#state` fields in a logic-consolidation CL that are only
|
|
82
|
+
written to in that CL and only read in the *next* CL's `DEFAULT_VIEW`.
|
|
83
|
+
* Either read them in the prefactor CL (e.g., inside an extracted imperative
|
|
84
|
+
`#render...` helper) or introduce them in the CL that actually adds
|
|
85
|
+
`DEFAULT_VIEW`.
|
|
86
|
+
|
|
87
|
+
7. **Extract Imperative `#renderSubtreeContent()` Helpers**:
|
|
88
|
+
|
|
89
|
+
* For complex layouts, extracting an imperative `#renderFooContent()`
|
|
90
|
+
helper in Pass 1 (while keeping the outer container imperative) minimizes
|
|
91
|
+
the Pass 2/3 diff when converting `#renderFooContent()` to
|
|
92
|
+
`renderFooContent(input: ViewInput): LitTemplate`.
|
|
93
|
+
|
|
94
|
+
8. **Preserve Imperative Cleanup Calls**:
|
|
95
|
+
|
|
96
|
+
* When consolidating DOM/widget references (e.g., replacing
|
|
97
|
+
`#tabbedLocationHeader` with `#debuggerSidebarTabbedLocation`), preserve
|
|
98
|
+
imperative cleanup calls like `splitWidget.uninstallResizer(...)` until the
|
|
99
|
+
imperative container itself is removed.
|
|
100
|
+
|
|
101
|
+
9. **Wait for confirmation**:
|
|
80
102
|
|
|
81
103
|
* Wait for an explicit confirmation from the user (or Parent Orchestrator Agent) before proceeding to the next step.
|
|
82
104
|
|
|
@@ -86,6 +108,8 @@ consolidating them into private helper methods with explicit data contracts.
|
|
|
86
108
|
|
|
87
109
|
*Before reporting back or committing, re-read the instructions and verify:*
|
|
88
110
|
1. ❓ **Minimal Diffs**: Did I move code blocks around unnecessarily?
|
|
89
|
-
2. ❓ **State Completeness**: Are all
|
|
90
|
-
3. ❓ **
|
|
111
|
+
2. ❓ **State Completeness & Utility**: Are all state variables actually read in this CL, with no write-only placeholders?
|
|
112
|
+
3. ❓ **Cleanup Preserved**: Are resizer/listener uninstallation calls preserved during reference consolidation?
|
|
113
|
+
4. ❓ **Build**: Does the code still compile?
|
|
114
|
+
|
|
91
115
|
|
|
@@ -76,6 +76,26 @@ principles.
|
|
|
76
76
|
clean and focused on high-level orchestration, prefer delegating intense
|
|
77
77
|
execution tasks (like running tests, debugging compilations, or processing
|
|
78
78
|
independent classes in parallel) to specialized subagents.
|
|
79
|
+
13. **Cross-Repo Runtime Coupling Check (`legacy_test_runner` & `blink/web_tests`)**:
|
|
80
|
+
Before converting TypeScript `private` fields/methods to ES `#private` or
|
|
81
|
+
removing widget properties (e.g., `.tabbedPane`, `.sidebarPaneStack`,
|
|
82
|
+
`.threadsSidebarPane`, `.searchInputElement`), search both
|
|
83
|
+
`front_end/legacy_test_runner/**/*.js` and Chromium's
|
|
84
|
+
`third_party/blink/web_tests/http/tests/devtools/**/*.js` (which run on
|
|
85
|
+
`dtf_linux_rel` CQ via `blink_web_tests` and reflectively access private
|
|
86
|
+
members in untyped JS). Keep accessed members as TypeScript `private` with a
|
|
87
|
+
`// Accessed in web_tests / legacy_test_runner` comment, or migrate the
|
|
88
|
+
web_tests first in a dedicated CL.
|
|
89
|
+
14. **Strict CL Splitting Rules**:
|
|
90
|
+
* **One Class Per CL for Hybrid Files**: When modernizing files containing
|
|
91
|
+
multiple classes, extract or migrate secondary classes (e.g.,
|
|
92
|
+
`QuickSourceView`) into their own dedicated CLs.
|
|
93
|
+
* **Separate Infrastructure CLs**: Keep shared `ui/legacy` or component
|
|
94
|
+
infrastructure changes (e.g., `bindToAction`, `SplitWidget`,
|
|
95
|
+
`WrapperWidget`) in separate, preceding CLs. Always verify with
|
|
96
|
+
`cog_api.py diff` before uploading that no changes from later commits
|
|
97
|
+
leaked into earlier infrastructure CLs.
|
|
98
|
+
|
|
79
99
|
|
|
80
100
|
|
|
81
101
|
--------------------------------------------------------------------------------
|
|
@@ -129,6 +149,8 @@ The plan you generate **MUST** follow this structure:
|
|
|
129
149
|
## 🛠️ Passes Checklist
|
|
130
150
|
- [ ] **Pass 0: Baseline & Safety Scaffolding**
|
|
131
151
|
- Assigned to: `ui-eng-vision-test-scaffolder`
|
|
152
|
+
- [ ] **Pass 0.5: Privatize & Dead-Code Cleanup**
|
|
153
|
+
- Mechanical prefactor: convert TypeScript `private` to ES `#private` and remove dead code
|
|
132
154
|
- [ ] **Pass 1: Logic Consolidation**
|
|
133
155
|
- Assigned to: `ui-eng-vision-logic-consolidator`
|
|
134
156
|
- [ ] **Pass 2: Lit-HTML Rendering**
|
|
@@ -149,10 +171,18 @@ The plan you generate **MUST** follow this structure:
|
|
|
149
171
|
regressions.
|
|
150
172
|
2. Pay extra attention not to skip this step.
|
|
151
173
|
|
|
174
|
+
### Privatize & Dead-Code Cleanup (Pass 0.5)
|
|
175
|
+
|
|
176
|
+
* **Trigger Condition**: Baseline tests green.
|
|
177
|
+
* **Actions**:
|
|
178
|
+
1. Perform the **Cross-Repo Runtime Coupling Check** against `legacy_test_runner` and `blink_web_tests` first.
|
|
179
|
+
2. Convert internal TypeScript `private` fields/methods to ES `#private` (except those reflectively accessed by web tests).
|
|
180
|
+
3. Identify and remove unused dead code, redundant containers, or obsolete split widgets in a dedicated prefactor CL so mechanical renames don't clutter logic and view CLs.
|
|
181
|
+
4. Verify that all tests and lints pass and stage this prefactor CL before proceeding to logic consolidation.
|
|
182
|
+
|
|
152
183
|
### Logic Consolidation
|
|
153
184
|
|
|
154
|
-
* **Trigger Condition**:
|
|
155
|
-
staged).
|
|
185
|
+
* **Trigger Condition**: Pass 0.5 staged (or baseline tests green if 0.5 skipped).
|
|
156
186
|
* **Subskill to Import/Delegate**: `ui-eng-vision-logic-consolidator` (Delegate to a subagent to maintain focus)
|
|
157
187
|
* **Actions**:
|
|
158
188
|
1. Extract manual elements, constructor DOM configurations, and imperative
|
|
@@ -65,20 +65,23 @@ logical regressions are introduced during modernization.
|
|
|
65
65
|
|
|
66
66
|
Always detect the execution environment first to run tests successfully:
|
|
67
67
|
|
|
68
|
-
### Scenario A: Google-Internal Cog
|
|
68
|
+
### Scenario A: Google-Internal Cog Workspaces (PRIMARY PATH for Google Workspaces)
|
|
69
69
|
|
|
70
70
|
* **Detection**: Triggered if the workspace path starts with `/google/cog/` or
|
|
71
71
|
`/google/src/`.
|
|
72
|
-
* **
|
|
73
|
-
because they lack Google cloudtop/virtualization wrapper configurations.
|
|
72
|
+
* **Execution Guide**: Follow [`devtools-verification`](../devtools-verification/SKILL.md).
|
|
74
73
|
* **Resolution Steps**:
|
|
75
74
|
|
|
76
|
-
1.
|
|
77
|
-
2. Leverage the Google-specific Cider testing script to compile and run
|
|
78
|
-
tests in the cloud workspace:
|
|
75
|
+
1. Ensure `depot_tools` is at the front of your `PATH`:
|
|
79
76
|
|
|
80
77
|
```bash
|
|
81
|
-
|
|
78
|
+
export PATH="/path/to/depot_tools:$PATH"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
2. Execute tests using `./dt test` directly from `chromium/third_party/devtools-frontend/src`:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
./dt test front_end/panels/application/IndexedDBViews.test.ts
|
|
82
85
|
```
|
|
83
86
|
|
|
84
87
|
### Scenario B: Standard Chromium Environment (Fallback)
|
|
@@ -133,6 +136,33 @@ Always detect the execution environment first to run tests successfully:
|
|
|
133
136
|
3. If it's a layout shift, check if you replaced a block element with an inline element (e.g. `div` -> `span`).
|
|
134
137
|
4. If styles are missing, verify if the test wrapper needs `{includeCommonStyles: true}`.
|
|
135
138
|
|
|
139
|
+
### Scenario E: Lightweight Test Hook Gotchas (`describe` vs `describeWithEnvironment`)
|
|
140
|
+
|
|
141
|
+
* Prefer `describe` with lightweight hooks (`setupLocaleHooks()`, `setupSettingsHooks()`) when full `describeWithEnvironment` is unnecessary.
|
|
142
|
+
* **Always reset singleton registries in `afterEach`**:
|
|
143
|
+
When registering actions, shortcuts, or context menus in tests, singleton registries MUST be reset in `afterEach`:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
afterEach(() => {
|
|
147
|
+
UI.ActionRegistration.reset();
|
|
148
|
+
UI.ActionRegistry.ActionRegistry.reset();
|
|
149
|
+
UI.ShortcutRegistry.ShortcutRegistry.removeInstance();
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Without these resets, repeated runs (such as `--repeat=10` or CQ flake detection) will fail on iteration 2+ with `Duplicate action ID: '...'`.
|
|
154
|
+
|
|
155
|
+
* **GlassPane Container Setup**:
|
|
156
|
+
If testing UI that opens a `GlassPane` or long-press option menu (e.g. `bindToAction` with long-press buttons) in a test suite using `describe` without `describeWithEnvironment`, call:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
beforeEach(() => {
|
|
160
|
+
UI.GlassPane.GlassPane.setContainer(document.body);
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Otherwise, menu rendering will fail because `GlassPane` lacks an attached container element.
|
|
165
|
+
|
|
136
166
|
--------------------------------------------------------------------------------
|
|
137
167
|
|
|
138
168
|
## 🔍 Mental Audit (Internal Self-Correction)
|
|
@@ -28,11 +28,11 @@ clean `UI.Widget` classes that use native update rendering delegates.
|
|
|
28
28
|
2. **Upgrade View Delegates & Inject View**:
|
|
29
29
|
|
|
30
30
|
* Migrate the local Lit template strings into a formally exported default
|
|
31
|
-
view layout:
|
|
31
|
+
view layout (using named imports `import {html, render} from '../../ui/lit/lit.js';`):
|
|
32
32
|
|
|
33
33
|
```typescript
|
|
34
34
|
export const DEFAULT_VIEW: View = (input, output, target) => {
|
|
35
|
-
|
|
35
|
+
render(html`...`, target);
|
|
36
36
|
};
|
|
37
37
|
```
|
|
38
38
|
|
|
@@ -67,10 +67,10 @@ clean `UI.Widget` classes that use native update rendering delegates.
|
|
|
67
67
|
|
|
68
68
|
* Remove any `LegacyWrapper` bindings or custom wrappable custom elements.
|
|
69
69
|
* Replace old panel instantiations with standard `<devtools-widget>`
|
|
70
|
-
declarative entries:
|
|
70
|
+
declarative entries (using `const {widget} = UI.Widget;`):
|
|
71
71
|
|
|
72
72
|
```typescript
|
|
73
|
-
html`<devtools-widget
|
|
73
|
+
html`<devtools-widget ${widget(MyWidgetClass, {params})}></devtools-widget>`
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
* Inspect instantiator files (e.g., `ApplicationPanelSidebar.ts`) and
|
|
@@ -43,7 +43,7 @@ Complex views can be broken down using helper functions defined inside `DEFAULT_
|
|
|
43
43
|
```ts
|
|
44
44
|
// clang-format off
|
|
45
45
|
const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLElement): void => {
|
|
46
|
-
const renderHeader = ():
|
|
46
|
+
const renderHeader = (): LitTemplate => {
|
|
47
47
|
return html`<h1>${input.title}</h1>`;
|
|
48
48
|
};
|
|
49
49
|
// ...
|
|
@@ -58,10 +58,11 @@ const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLElement)
|
|
|
58
58
|
|
|
59
59
|
### Composition
|
|
60
60
|
|
|
61
|
-
* To render a child widget, the parent's View MUST use lit-html directive `UI.Widget.
|
|
61
|
+
* To render a child widget, the parent's View MUST use the lit-html directive `UI.Widget.widget`.
|
|
62
62
|
* Configuration is passed via the parameters.
|
|
63
63
|
* Properties passed from a parent MUST be declared as public fields on the child presenter class.
|
|
64
64
|
* The framework automatically updates these properties and calls `requestUpdate()` on the child when the parent re-renders.
|
|
65
|
+
* **Pre-existing Widget Instances (`WrapperWidget`):** To render an existing `UI.Widget` instance (such as locations created by `UI.ViewManager`: `TabbedViewLocation.tabbedPane()`, `StackLocation.widget()`) declaratively without creating a custom wrapper class, use `${widget(UI.Widget.WrapperWidget, {widget: myWidgetInstance})}`. `WrapperWidget` automatically forwards constraints, handles attach/detach lifecycle, and supports dynamic widget replacement.
|
|
65
66
|
|
|
66
67
|
## Refactoring Legacy Components
|
|
67
68
|
|
|
@@ -106,7 +107,7 @@ render(html`
|
|
|
106
107
|
**Note:** The `{host: input}` option in `render()` is generally **not required** unless you are using specific Shadow DOM patterns that necessitate it. The `@scope` strategy is the preferred method for style isolation in DevTools widgets.
|
|
107
108
|
|
|
108
109
|
### Legacy Interop & Refs
|
|
109
|
-
* **Raw Elements:** Use `
|
|
110
|
+
* **Raw Elements:** Use `Directives.ref` (from `ui/lit/lit.js`) to obtain a reference to a raw `HTMLElement` if needed for imperative APIs (e.g., `splitWidget.installResizer(element)`).
|
|
110
111
|
* **Child Widgets:** Use `UI.Widget.widgetRef` to obtain the class instance of a child widget if you need to call methods on it directly (though declarative data flow is preferred).
|
|
111
112
|
|
|
112
113
|
### Dependencies
|
|
@@ -153,12 +154,12 @@ Example: `front_end/panels/my_example/MyExampleWidget.ts`.
|
|
|
153
154
|
```ts
|
|
154
155
|
// front_end/panels/my_example/MyExampleWidget.ts
|
|
155
156
|
|
|
156
|
-
import
|
|
157
|
+
import {Directives, html, render} from '../../ui/lit/lit.js';
|
|
157
158
|
import * as UI from '../../ui/legacy/legacy.js';
|
|
158
159
|
|
|
159
160
|
import myExampleWidgetStyles from './myExampleWidget.css.js';
|
|
160
161
|
|
|
161
|
-
const {
|
|
162
|
+
const {ref} = Directives;
|
|
162
163
|
|
|
163
164
|
// 1. Define Input data shape. (Assuming Child Widget configuration for completeness)
|
|
164
165
|
export interface ViewInput {
|
|
@@ -241,14 +242,13 @@ Example: `front_end/panels/parent/ParentWidget.ts`. Demonstrates passing state v
|
|
|
241
242
|
```ts
|
|
242
243
|
// front_end/panels/parent/ParentWidget.ts
|
|
243
244
|
|
|
244
|
-
import
|
|
245
|
+
import {html, render} from '../../ui/lit/lit.js';
|
|
245
246
|
import * as UI from '../../ui/legacy/legacy.js';
|
|
246
247
|
import {MyExampleWidget} from '../my_example/MyExampleWidget.ts';
|
|
247
248
|
|
|
248
249
|
import parentWidgetStyles from './parentWidgetStyles.css';
|
|
249
250
|
|
|
250
|
-
const {
|
|
251
|
-
const {widgetConfig} = UI.Widget;
|
|
251
|
+
const {widget} = UI.Widget;
|
|
252
252
|
|
|
253
253
|
interface ViewInput {
|
|
254
254
|
title: string;
|
package/docs/ui_engineering.md
CHANGED
|
@@ -906,6 +906,34 @@ export const DEFAULT_VIEW = (input, _output, target) => {
|
|
|
906
906
|
};
|
|
907
907
|
```
|
|
908
908
|
|
|
909
|
+
### Long-press action buttons
|
|
910
|
+
|
|
911
|
+
Imperative long-press action buttons (`UI.Toolbar.Toolbar.createLongPressActionButton`) are migrated declaratively by nesting secondary `<devtools-button>` elements inside `<devtools-button ${bindToAction('...')}>`. The parent button triggers the primary action on click, and holding the button down opens a menu presenting the nested buttons:
|
|
912
|
+
|
|
913
|
+
```typescript
|
|
914
|
+
export const DEFAULT_VIEW = (input, _output, target) => {
|
|
915
|
+
const {bindToAction} = UI.UIUtils;
|
|
916
|
+
render(html`
|
|
917
|
+
<devtools-toolbar>
|
|
918
|
+
<devtools-button ${bindToAction('debugger.toggle-pause')}>
|
|
919
|
+
<devtools-button
|
|
920
|
+
.iconName=${'stop'}
|
|
921
|
+
.variant=${Buttons.Button.Variant.TOOLBAR}
|
|
922
|
+
.title=${i18nString(UIStrings.terminateCurrentJavascriptCall)}
|
|
923
|
+
@click=${input.onTerminateExecution}>
|
|
924
|
+
</devtools-button>
|
|
925
|
+
<devtools-button
|
|
926
|
+
.iconName=${'play'}
|
|
927
|
+
.variant=${Buttons.Button.Variant.TOOLBAR}
|
|
928
|
+
.title=${i18nString(UIStrings.resumeWithAllPausesBlockedForMs)}
|
|
929
|
+
@click=${input.onLongResume}>
|
|
930
|
+
</devtools-button>
|
|
931
|
+
</devtools-button>
|
|
932
|
+
</devtools-toolbar>`,
|
|
933
|
+
target);
|
|
934
|
+
};
|
|
935
|
+
```
|
|
936
|
+
|
|
909
937
|
## Binding a setting to a checkbox
|
|
910
938
|
|
|
911
939
|
Use the `bindToSetting` directive to bind a boolean setting to a `<devtools-checkbox>` component.
|
|
@@ -1244,6 +1272,12 @@ export const DEFAULT_VIEW = (input, _output, target) => {
|
|
|
1244
1272
|
};
|
|
1245
1273
|
```
|
|
1246
1274
|
|
|
1275
|
+
### Pre-existing Widget Instances (`WrapperWidget`)
|
|
1276
|
+
When embedding pre-existing `UI.Widget` instances that are created elsewhere (such as `UI.ViewManager` locations: `TabbedViewLocation.tabbedPane()`, `StackLocation.widget()`), render them declaratively using `${widget(UI.Widget.WrapperWidget, {widget: myWidgetInstance})}` instead of creating custom wrapper widget classes. `WrapperWidget` automatically forwards constraints and manages child widget attach/detach lifecycles.
|
|
1277
|
+
|
|
1278
|
+
### Preserving `showMode` Settings
|
|
1279
|
+
When migrating a `SplitWidget` that persists sidebar visibility (e.g. via `splitWidget.showModeSetting()`), bind the setting's value to the `<devtools-split-view show=${input.showMode}>` property (`Both`, `OnlyMain`, or `OnlySidebar`) to preserve user settings and layout state across sessions.
|
|
1280
|
+
|
|
1247
1281
|
## Migrating `UI.ARIAUtils` helpers
|
|
1248
1282
|
|
|
1249
1283
|
Replace calls to `UI.ARIAUtils` helper functions with the corresponding ARIA attributes directly in the lit-html template.
|
|
@@ -258,6 +258,11 @@ export class Settings {
|
|
|
258
258
|
return setting;
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
/** The event target shared by all settings. Change listeners are registered with the setting name as event type. */
|
|
262
|
+
get eventTargetForTest(): ObjectWrapper<GenericEvents> {
|
|
263
|
+
return this.#eventSupport;
|
|
264
|
+
}
|
|
265
|
+
|
|
261
266
|
/**
|
|
262
267
|
* Get setting via key, and create a new setting if the requested setting does not exist.
|
|
263
268
|
* @param key kebab-case string ID
|
|
@@ -636,6 +636,7 @@ export enum IssueCreated {
|
|
|
636
636
|
'GenericIssue::AutofillAndManualTextPolicyControlledFeaturesInfo' = 114,
|
|
637
637
|
'GenericIssue::AutofillPolicyControlledFeatureInfo' = 115,
|
|
638
638
|
'GenericIssue::ManualTextPolicyControlledFeatureInfo' = 116,
|
|
639
|
+
'GenericIssue::GeolocationPromptWithoutUserGesture' = 117,
|
|
639
640
|
'CorsIssue::InvalidLocalNetworkAccess' = 118,
|
|
640
641
|
/* eslint-enable @typescript-eslint/naming-convention */
|
|
641
642
|
MAX_VALUE = 119,
|
|
@@ -537,6 +537,24 @@ export class CSSModel extends SDKModel<EventTypes> {
|
|
|
537
537
|
return true;
|
|
538
538
|
}
|
|
539
539
|
|
|
540
|
+
forcePositionTryOption(node: DOMNode, index?: number): boolean {
|
|
541
|
+
const currentForcedIndex = node.marker<number>(PositionTryOptionMarker) ?? undefined;
|
|
542
|
+
if (currentForcedIndex === index) {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
node.setMarker(PositionTryOptionMarker, index ?? null);
|
|
546
|
+
if (node.id === undefined) {
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
void this.agent.invoke_forcePositionTryOption({nodeId: node.id, index});
|
|
550
|
+
this.dispatchEventToListeners(Events.PositionTryOptionForced, node);
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
forcedPositionTryOption(node: DOMNode): number|undefined {
|
|
555
|
+
return node.marker<number>(PositionTryOptionMarker) ?? undefined;
|
|
556
|
+
}
|
|
557
|
+
|
|
540
558
|
forcePseudoState(node: DOMNode, pseudoClass: string, enable: boolean): boolean {
|
|
541
559
|
const forcedPseudoClasses = node.marker<string[]>(PseudoStateMarker) || [];
|
|
542
560
|
const hasPseudoClass = forcedPseudoClasses.includes(pseudoClass);
|
|
@@ -1058,6 +1076,7 @@ export enum Events {
|
|
|
1058
1076
|
MediaQueryResultChanged = 'MediaQueryResultChanged',
|
|
1059
1077
|
ModelWasEnabled = 'ModelWasEnabled',
|
|
1060
1078
|
ModelDisposed = 'ModelDisposed',
|
|
1079
|
+
PositionTryOptionForced = 'PositionTryOptionForced',
|
|
1061
1080
|
PseudoStateForced = 'PseudoStateForced',
|
|
1062
1081
|
StartingStylesStateForced = 'StartingStylesStateForced',
|
|
1063
1082
|
StyleSheetAdded = 'StyleSheetAdded',
|
|
@@ -1087,6 +1106,7 @@ export interface EventTypes {
|
|
|
1087
1106
|
[Events.MediaQueryResultChanged]: void;
|
|
1088
1107
|
[Events.ModelWasEnabled]: void;
|
|
1089
1108
|
[Events.ModelDisposed]: CSSModel;
|
|
1109
|
+
[Events.PositionTryOptionForced]: DOMNode;
|
|
1090
1110
|
[Events.PseudoStateForced]: PseudoStateForcedEvent;
|
|
1091
1111
|
[Events.StartingStylesStateForced]: DOMNode;
|
|
1092
1112
|
[Events.StyleSheetAdded]: CSSStyleSheetHeader;
|
|
@@ -1096,6 +1116,7 @@ export interface EventTypes {
|
|
|
1096
1116
|
}
|
|
1097
1117
|
|
|
1098
1118
|
const PseudoStateMarker = 'pseudo-state-marker';
|
|
1119
|
+
const PositionTryOptionMarker = 'position-try-option-marker';
|
|
1099
1120
|
|
|
1100
1121
|
export class Edit {
|
|
1101
1122
|
styleSheetId: string;
|
|
@@ -341,7 +341,7 @@ class ComputedTextChunk {
|
|
|
341
341
|
* original and computed slices as required.
|
|
342
342
|
**/
|
|
343
343
|
export class ComputedText {
|
|
344
|
-
|
|
344
|
+
#chunks: ComputedTextChunk[] = [];
|
|
345
345
|
readonly text: string;
|
|
346
346
|
readonly #topLevelValueCounts = new Map<string, number>();
|
|
347
347
|
|
|
@@ -356,6 +356,7 @@ export class ComputedText {
|
|
|
356
356
|
}
|
|
357
357
|
|
|
358
358
|
get chunkCount(): number {
|
|
359
|
+
this.#sortIfNecessary();
|
|
359
360
|
return this.#chunks.length;
|
|
360
361
|
}
|
|
361
362
|
|
|
@@ -363,6 +364,14 @@ export class ComputedText {
|
|
|
363
364
|
if (this.#sorted) {
|
|
364
365
|
return;
|
|
365
366
|
}
|
|
367
|
+
// A match that was pushed more than once is superseded by its last push.
|
|
368
|
+
const lastPush = new Map<Match, ComputedTextChunk>();
|
|
369
|
+
for (const chunk of this.#chunks) {
|
|
370
|
+
lastPush.set(chunk.match, chunk);
|
|
371
|
+
}
|
|
372
|
+
if (lastPush.size < this.#chunks.length) {
|
|
373
|
+
this.#chunks = this.#chunks.filter(chunk => lastPush.get(chunk.match) === chunk);
|
|
374
|
+
}
|
|
366
375
|
// Sort intervals by offset, with longer intervals first if the offset is identical.
|
|
367
376
|
this.#chunks.sort((a, b) => {
|
|
368
377
|
if (a.offset < b.offset) {
|
|
@@ -383,7 +392,7 @@ export class ComputedText {
|
|
|
383
392
|
}
|
|
384
393
|
|
|
385
394
|
// Add another substitutable match. The match will either be appended to the list of existing matches or it will
|
|
386
|
-
// be substituted for the last match(es) if it encompasses them.
|
|
395
|
+
// be substituted for the last match(es) if it encompasses them. Pushing a match again supersedes its earlier push.
|
|
387
396
|
push(match: Match, offset: number): void {
|
|
388
397
|
function hasComputedText(match: Match): match is MatchWithComputedText {
|
|
389
398
|
return Boolean(match.computedText);
|
|
@@ -1188,7 +1188,7 @@ export class DOMNode extends Common.ObjectWrapper.ObjectWrapper<DOMNodeEventType
|
|
|
1188
1188
|
}
|
|
1189
1189
|
|
|
1190
1190
|
marker<T>(name: string): T|null {
|
|
1191
|
-
return this.#markers.get(name) as T
|
|
1191
|
+
return (this.#markers.get(name) as T) ?? null;
|
|
1192
1192
|
}
|
|
1193
1193
|
|
|
1194
1194
|
getMarkerKeysForTest(): string[] {
|
|
@@ -959,6 +959,9 @@ export class DebuggerModel extends SDKModel<EventTypes> {
|
|
|
959
959
|
this.#disableAsyncStackTracesSetting.removeChangeListener(this.asyncStackTracesStateChanged, this);
|
|
960
960
|
this.#breakpointsActiveSetting.removeChangeListener(this.breakpointsActiveChanged, this);
|
|
961
961
|
this.#jsSourceMapsEnabledSetting.removeChangeListener(this.jsSourceMapsStateChanged, this);
|
|
962
|
+
// Drop all scripts and their source maps, so they don't outlive the target in case something
|
|
963
|
+
// still holds on to this model (e.g. a console message with "Preserve log" enabled).
|
|
964
|
+
this.reset();
|
|
962
965
|
}
|
|
963
966
|
|
|
964
967
|
override async suspendModel(): Promise<void> {
|