chrome-devtools-frontend 1.0.1581449 → 1.0.1582745
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/prompts/merging-devtools-module.md +144 -0
- package/agents/prompts/ui-widgets.md +351 -0
- package/agents/prompts/verification.md +2 -1
- package/docs/contributing/README.md +5 -6
- package/docs/contributing/changes.md +1 -2
- package/docs/styleguide/ux/README.md +1 -1
- package/front_end/core/sdk/OverlayModel.ts +4 -2
- package/front_end/core/sdk/RemoteObject.ts +7 -1
- package/front_end/core/sdk/StorageKeyManager.ts +6 -1
- package/front_end/core/sdk/Target.ts +4 -2
- package/front_end/entrypoint_template.html +5 -1
- package/front_end/entrypoints/greendev_floaty/FloatyEntrypoint.ts +31 -40
- package/front_end/entrypoints/greendev_floaty/floaty.css +41 -1
- package/front_end/entrypoints/greendev_floaty/floaty.html +8 -1
- package/front_end/entrypoints/greendev_floaty/greendev_floaty.ts +5 -5
- package/front_end/entrypoints/node_app/app/NodeMain.ts +19 -1
- package/front_end/entrypoints/node_app/node_app.ts +34 -0
- package/front_end/generated/InspectorBackendCommands.ts +1 -1
- package/front_end/generated/SupportedCSSProperties.js +2 -0
- package/front_end/generated/protocol.ts +0 -6
- package/front_end/models/ai_assistance/AiConversation.ts +10 -0
- package/front_end/models/ai_assistance/agents/AiAgent.ts +2 -0
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.snapshot.txt +26 -4
- package/front_end/models/ai_assistance/agents/ContextSelectionAgent.ts +85 -7
- package/front_end/models/ai_assistance/agents/NetworkAgent.ts +2 -6
- package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +66 -2
- package/front_end/models/computed_style/ComputedStyleModel.ts +26 -0
- package/front_end/models/greendev/Prototypes.ts +1 -10
- package/front_end/models/issues_manager/ConnectionAllowlistIssue.ts +75 -0
- package/front_end/models/issues_manager/CookieIssue.ts +0 -28
- package/front_end/models/issues_manager/FederatedAuthRequestIssue.ts +0 -30
- package/front_end/models/issues_manager/IssuesManager.ts +5 -0
- package/front_end/models/issues_manager/descriptions/connectionAllowlistInvalidAllowlistItemType.md +12 -0
- package/front_end/models/issues_manager/descriptions/connectionAllowlistInvalidHeader.md +12 -0
- package/front_end/models/issues_manager/descriptions/connectionAllowlistInvalidUrlPattern.md +8 -0
- package/front_end/models/issues_manager/descriptions/connectionAllowlistItemNotInnerList.md +12 -0
- package/front_end/models/issues_manager/descriptions/connectionAllowlistMoreThanOneList.md +7 -0
- package/front_end/models/issues_manager/descriptions/connectionAllowlistReportingEndpointNotToken.md +10 -0
- package/front_end/models/issues_manager/issues_manager.ts +2 -0
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +93 -6
- package/front_end/panels/ai_assistance/components/ChatInput.ts +8 -4
- package/front_end/panels/application/ApplicationPanelSidebar.ts +13 -11
- package/front_end/panels/application/DOMStorageModel.ts +1 -1
- package/front_end/panels/application/ResourcesPanel.ts +10 -5
- package/front_end/panels/application/preloading/PreloadingView.ts +8 -1
- package/front_end/panels/application/preloading/components/PreloadingDetailsReportView.ts +4 -1
- package/front_end/panels/application/preloading/components/PreloadingGrid.ts +2 -1
- package/front_end/panels/application/preloading/components/PreloadingString.ts +12 -3
- package/front_end/panels/application/preloading/helper/PreloadingForward.ts +14 -0
- package/front_end/panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts +37 -3
- package/front_end/panels/changes/ChangesSidebar.ts +2 -6
- package/front_end/panels/common/AiCodeCompletionTeaser.ts +13 -3
- package/front_end/panels/common/aiCodeCompletionTeaser.css +6 -0
- package/front_end/panels/console/ConsoleSidebar.ts +3 -11
- package/front_end/panels/console_counters/WarningErrorCounter.ts +16 -10
- package/front_end/panels/elements/ComputedStyleWidget.ts +55 -37
- package/front_end/panels/elements/PlatformFontsWidget.ts +23 -10
- package/front_end/panels/greendev/GreenDevPanel.css +42 -1
- package/front_end/panels/greendev/GreenDevPanel.ts +30 -1
- package/front_end/panels/lighthouse/LighthouseStartView.ts +3 -5
- package/front_end/panels/lighthouse/lighthouseStartView.css +6 -0
- package/front_end/panels/network/NetworkLogView.ts +6 -6
- package/front_end/panels/network/RequestInitiatorView.ts +27 -19
- package/front_end/panels/network/RequestTimingView.ts +1 -1
- package/front_end/panels/settings/AISettingsTab.ts +1 -5
- package/front_end/panels/settings/KeybindsSettingsTab.ts +4 -3
- package/front_end/panels/settings/SettingsScreen.ts +0 -51
- package/front_end/panels/sources/OutlineQuickOpen.ts +19 -0
- package/front_end/panels/timeline/AnimationsTrackAppender.ts +4 -1
- package/front_end/panels/timeline/InteractionsTrackAppender.ts +1 -1
- package/front_end/panels/timeline/TimelinePanel.ts +25 -0
- package/front_end/panels/timeline/TimelineUIUtils.ts +13 -16
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/lighthouse/README.chromium +2 -2
- package/front_end/third_party/lighthouse/lighthouse-dt-bundle.js +145 -144
- package/front_end/third_party/lighthouse/report/bundle.js +12 -5
- package/front_end/third_party/lighthouse/report-assets/report-generator.mjs +2 -2
- package/front_end/ui/components/markdown_view/MarkdownLinksMap.ts +5 -1
- package/front_end/ui/legacy/ListControl.ts +28 -1
- package/front_end/ui/legacy/Toolbar.ts +4 -4
- package/front_end/ui/legacy/Treeoutline.ts +5 -5
- package/front_end/ui/legacy/UIUtils.ts +26 -10
- package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +10 -13
- package/front_end/ui/legacy/components/utils/Linkifier.ts +4 -7
- package/front_end/ui/visual_logging/KnownContextValues.ts +2 -0
- package/inspector_overlay/main.ts +18 -3
- package/inspector_overlay/tool_green_dev_anchors.css +54 -0
- package/inspector_overlay/tool_green_dev_anchors.ts +164 -0
- package/inspector_overlay/tool_persistent.ts +14 -0
- package/package.json +1 -1
- package/docs/contributing/design.md +0 -166
- package/docs/design_guidelines.md +0 -1
- package/front_end/models/issues_manager/descriptions/federatedAuthRequestClientMetadataHttpNotFound.md +0 -1
- package/front_end/models/issues_manager/descriptions/federatedAuthRequestClientMetadataInvalidResponse.md +0 -1
- package/front_end/models/issues_manager/descriptions/federatedAuthRequestClientMetadataNoResponse.md +0 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Workflow: Merging a DevTools Submodule into its Parent
|
|
2
|
+
|
|
3
|
+
This document outlines the process for merging a submodule (e.g., `panels/timeline/extensions`) into its parent module (e.g., `panels/timeline`) within the DevTools build system. The goal is to simplify the build configuration by consolidating `BUILD.gn` files while keeping the original source file directory structure.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
You will need the following information:
|
|
8
|
+
- **Parent Module Path:** The path to the directory containing the primary module.
|
|
9
|
+
- **Child Module Path:** The path to the directory of the submodule to be merged.
|
|
10
|
+
|
|
11
|
+
## Workflow Steps
|
|
12
|
+
|
|
13
|
+
### 1. Analyze Build Configurations
|
|
14
|
+
|
|
15
|
+
Read the contents of the `BUILD.gn` file from both the child module and the parent module. Identify the following from the child's `BUILD.gn`:
|
|
16
|
+
- The list of `sources` in the `devtools_module`.
|
|
17
|
+
- The list of `deps` (dependencies) in the `devtools_module`.
|
|
18
|
+
- The `entrypoint` for the `devtools_entrypoint("bundle")`.
|
|
19
|
+
|
|
20
|
+
### 2. Modify the Parent `BUILD.gn`
|
|
21
|
+
|
|
22
|
+
Edit the `BUILD.gn` file in the parent module's directory to incorporate the child module's configuration.
|
|
23
|
+
|
|
24
|
+
1. **Add Child's Sources:** Append the list of `sources` from the child's `devtools_module` to the parent's `sources` list. Remember to maintain the relative path from the parent's directory (e.g., `extensions/ExtensionUI.ts`).
|
|
25
|
+
2. **Merge Dependencies:** Add the `deps` from the child's `devtools_module` to the parent's `deps` list. Remove any duplicate entries.
|
|
26
|
+
3. **Remove Child Bundle Dependency:** Delete the dependency on the child's bundle from the parent's `deps` list (e.g., remove `./extensions:bundle`).
|
|
27
|
+
|
|
28
|
+
### 3. Delete the Child `BUILD.gn`
|
|
29
|
+
|
|
30
|
+
Once the parent `BUILD.gn` is updated and contains all the necessary information, the child's `BUILD.gn` is no longer needed. Delete it.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
rm <child_module_path>/BUILD.gn
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 4. Update `devtools_grd_files.gni`
|
|
37
|
+
|
|
38
|
+
The global `.gni` file that lists all resources needs to be updated to reflect that the child module is no longer a separate, bundled entrypoint.
|
|
39
|
+
|
|
40
|
+
1. **Locate and Read the File:** Open `config/gni/devtools_grd_files.gni`.
|
|
41
|
+
2. **Remove Bundled Source:** Find and remove the line for the child's bundled JavaScript file from the `grd_files_bundled_sources` list. This path usually corresponds to the child's `entrypoint`.
|
|
42
|
+
3. **Add Unbundled Sources:** Add the paths to all of the child's original TypeScript source files (`.ts`) to the `grd_files_unbundled_sources` list.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Example: Merging `panels/timeline/extensions` into `panels/timeline`
|
|
47
|
+
|
|
48
|
+
- **Parent Module:** `panels/timeline`
|
|
49
|
+
- **Child Module:** `panels/timeline/extensions`
|
|
50
|
+
|
|
51
|
+
### 1. `panels/timeline/BUILD.gn` Modification
|
|
52
|
+
|
|
53
|
+
**Before:**
|
|
54
|
+
```gni
|
|
55
|
+
devtools_module("timeline") {
|
|
56
|
+
sources = [
|
|
57
|
+
...
|
|
58
|
+
"UIDevtoolsUtils.ts",
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
deps = [
|
|
62
|
+
...
|
|
63
|
+
"./components:bundle",
|
|
64
|
+
"./extensions:bundle",
|
|
65
|
+
"./overlays:bundle",
|
|
66
|
+
...
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**After:**
|
|
72
|
+
```gni
|
|
73
|
+
devtools_module("timeline") {
|
|
74
|
+
sources = [
|
|
75
|
+
...
|
|
76
|
+
"UIDevtoolsUtils.ts",
|
|
77
|
+
"extensions/ExtensionUI.ts", # Added from child
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
deps = [
|
|
81
|
+
...
|
|
82
|
+
"./components:bundle",
|
|
83
|
+
# "./extensions:bundle", # Removed
|
|
84
|
+
"./overlays:bundle",
|
|
85
|
+
...
|
|
86
|
+
# Dependencies from extensions/BUILD.gn are merged here
|
|
87
|
+
"../../../ui/components/helpers:bundle",
|
|
88
|
+
"../../../ui/components/render_coordinator:bundle",
|
|
89
|
+
"../../../ui/legacy:bundle",
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 2. `panels/timeline/extensions/BUILD.gn` Deletion
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
rm front_end/panels/timeline/extensions/BUILD.gn
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 3. `config/gni/devtools_grd_files.gni` Modification
|
|
101
|
+
|
|
102
|
+
**Before:**
|
|
103
|
+
```gni
|
|
104
|
+
grd_files_bundled_sources = [
|
|
105
|
+
...
|
|
106
|
+
"front_end/panels/timeline/components/components.js",
|
|
107
|
+
"front_end/panels/timeline/extensions/extensions.js",
|
|
108
|
+
"front_end/panels/timeline/overlays/overlays.js",
|
|
109
|
+
...
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
grd_files_unbundled_sources = [
|
|
113
|
+
...
|
|
114
|
+
"front_end/panels/timeline/extensions/ExtensionUI.ts", # This might not have been present before
|
|
115
|
+
...
|
|
116
|
+
]
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**After:**
|
|
120
|
+
```gni
|
|
121
|
+
grd_files_bundled_sources = [
|
|
122
|
+
...
|
|
123
|
+
"front_end/panels/timeline/components/components.js",
|
|
124
|
+
# "front_end/panels/timeline/extensions/extensions.js", # Removed
|
|
125
|
+
"front_end/panels/timeline/overlays/overlays.js",
|
|
126
|
+
...
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
grd_files_unbundled_sources = [
|
|
130
|
+
...
|
|
131
|
+
"front_end/panels/timeline/extensions/extensions.ts", # Added
|
|
132
|
+
"front_end/panels/timeline/extensions/ExtensionUI.ts", # Added
|
|
133
|
+
...
|
|
134
|
+
]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Removing Barrel Files
|
|
138
|
+
|
|
139
|
+
After merging modules, you may still have remaining barrel files (e.g. `index.ts` or `extensions.ts` that just re-export other files). These should be removed to simplify the module structure.
|
|
140
|
+
|
|
141
|
+
Manually updating all imports that rely on these barrels can be tedious and error-prone. The tool [unbarrelify](https://github.com/webpro/unbarrelify) can automate this process. It analyzes your codebase and replaces imports from barrel files with direct imports from the source files.
|
|
142
|
+
|
|
143
|
+
**Usage:**
|
|
144
|
+
Follow the instructions in the [unbarrelify repository](https://github.com/webpro/unbarrelify) to install and run the tool on your project. This is highly recommended to complete the refactoring process efficiently.
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
## UI Widget Framework Guide (MVP Architecture)
|
|
2
|
+
|
|
3
|
+
Adhere strictly to the Model-View-Presenter (MVP) architecture.
|
|
4
|
+
|
|
5
|
+
* **Models** (`front_end/models/`): Handle business logic and application state. MUST NOT have knowledge of the UI.
|
|
6
|
+
* **Presenters (`Widget`)**: Orchestrate data flow, manage UI-specific state, and handle interactions.
|
|
7
|
+
* **Views (`function`)**: Purely presentational. Stateless functions that receive data/callbacks and render using `lit-html`.
|
|
8
|
+
|
|
9
|
+
**Refactoring Note:** When refactoring legacy widgets to this framework, the widget's class name MUST NOT be changed. If you're refactoring a UI piece that uses shadow root (e.g. custom HTMLElements) to a Widget, make sure to update its styles to wrap the rules with a `@scope to (devtools-widget > *)` rule.
|
|
10
|
+
|
|
11
|
+
### Presenter (`Widget`) Rules
|
|
12
|
+
|
|
13
|
+
* Location: Co-located in the same file as its View.
|
|
14
|
+
* MUST extend a base `UI.Widget` class (e.g., `UI.Widget.Widget`). Note that `UI.Widget.Widget` is not an `HTMLElement` and must be appended via `.show()` or `<devtools-widget>`.
|
|
15
|
+
* Constructor MUST assign the injected view function to a private `#view` field.
|
|
16
|
+
* Constructor MUST call `super()`. If taking `element?: HTMLElement`, pass it to `super(element)`. `super(true)` is forbidden.
|
|
17
|
+
* Styling MUST be handled within the View. `this.registerCSSFiles()` is forbidden.
|
|
18
|
+
* Initial render MUST be triggered in `override wasShown(): void` by calling `this.requestUpdate()`.
|
|
19
|
+
* MUST hold all UI state as private class fields (e.g., `#counter`).
|
|
20
|
+
* To schedule a UI update, MUST call `this.requestUpdate()`. `performUpdate()` MUST NOT be called directly.
|
|
21
|
+
|
|
22
|
+
### View (`function`) Rules
|
|
23
|
+
|
|
24
|
+
* MUST be a pure function, typically named `DEFAULT_VIEW`, in the same file as the Presenter.
|
|
25
|
+
* MUST use the signature: `(input: ViewInput, output: ViewOutput, target: HTMLElement)`.
|
|
26
|
+
* `input`: Data and event handlers from the Presenter.
|
|
27
|
+
* `output`: Callbacks for imperative actions (e.g., focus) provided to the Presenter. If unused, the `output` parameter should be typed as `undefined` in the view's signature, and `undefined` should be passed from the presenter.
|
|
28
|
+
* `target`: The HTML element to render into.
|
|
29
|
+
* The `input` parameter MUST NOT be destructured. Always use `input.propertyName`.
|
|
30
|
+
* Responsibility: Render a `lit-html` template into `target`.
|
|
31
|
+
* MUST NOT hold state, fetch data, or perform business logic.
|
|
32
|
+
|
|
33
|
+
#### Helper Functions in Views
|
|
34
|
+
|
|
35
|
+
Complex views can be broken down using helper functions defined inside `DEFAULT_VIEW` to access `input` and `output`.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// clang-format off
|
|
39
|
+
const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLElement): void => {
|
|
40
|
+
const renderHeader = (): Lit.LitTemplate => {
|
|
41
|
+
return html`<h1>${input.title}</h1>`;
|
|
42
|
+
};
|
|
43
|
+
// ...
|
|
44
|
+
render(html`
|
|
45
|
+
<div class="child-widget">
|
|
46
|
+
${renderHeader()}
|
|
47
|
+
</div>
|
|
48
|
+
`, target);
|
|
49
|
+
};
|
|
50
|
+
// clang-format on
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Composition (`<devtools-widget>`)
|
|
54
|
+
|
|
55
|
+
* To render a child widget, the parent's View MUST use `<devtools-widget>`.
|
|
56
|
+
* Configuration is passed via the `.widgetConfig` property using `UI.Widget.widgetConfig()`.
|
|
57
|
+
* Properties passed from a parent MUST be declared as public fields on the child presenter class.
|
|
58
|
+
* The framework automatically updates these properties and calls `requestUpdate()` on the child when the parent re-renders.
|
|
59
|
+
|
|
60
|
+
## Refactoring Legacy Components
|
|
61
|
+
|
|
62
|
+
When migrating imperative components (extending `UI.VBox`, `UI.Panel`, or `HTMLElement`) to the MVP architecture:
|
|
63
|
+
|
|
64
|
+
1. **Analyze Rendering Logic:** Identify where DOM is created (constructor, `wasShown`, imperative methods). This logic moves to the `DEFAULT_VIEW`.
|
|
65
|
+
2. **Convert Base Class:**
|
|
66
|
+
* Prefer extending `UI.Widget.Widget`.
|
|
67
|
+
* **Special Case:** If the component *must* extend `UI.Panel.Panel` or `UI.Dialog.Dialog` (to retain specific functionality), you cannot use `requestUpdate()`. Instead, call `this.performUpdate()` directly on state changes.
|
|
68
|
+
3. **State Migration:** Move DOM-stored state to private class fields.
|
|
69
|
+
4. **Update Usage:** Replace `new MyComponent()` instantiations with declarative `<devtools-widget .widgetConfig=...>` in parent templates.
|
|
70
|
+
5. **Scoping Styles:** Ensure your CSS file uses the `@scope` block to prevent style leaks:
|
|
71
|
+
```css
|
|
72
|
+
@scope to (devtools-widget > *) {
|
|
73
|
+
...
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Key Implementation Details (Gotchas)
|
|
78
|
+
|
|
79
|
+
### Styling Strategy
|
|
80
|
+
DevTools widgets typically render into Light DOM (the default for `UI.Widget`). To ensure styles are scoped to the component and do not leak into child widgets, wrap your CSS in an `@scope` block:
|
|
81
|
+
|
|
82
|
+
```css
|
|
83
|
+
/* myWidget.css */
|
|
84
|
+
@scope to (devtools-widget > *) {
|
|
85
|
+
.my-class { ... }
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Then, include the styles in your `DEFAULT_VIEW`:
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import myWidgetStyles from './myWidget.css.js';
|
|
93
|
+
// ...
|
|
94
|
+
render(html`
|
|
95
|
+
<style>${myWidgetStyles}</style>
|
|
96
|
+
<div class="my-widget">...</div>
|
|
97
|
+
`, target);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**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.
|
|
101
|
+
|
|
102
|
+
### Legacy Interop & Refs
|
|
103
|
+
* **Raw Elements:** Use `Lit.Directives.ref` to obtain a reference to a raw `HTMLElement` if needed for imperative APIs (e.g., `splitWidget.installResizer(element)`).
|
|
104
|
+
* **Child Widgets:** Use `UI.Widget.widgetRef` to obtain the class instance of a child `<devtools-widget>` if you need to call methods on it directly (though declarative data flow is preferred).
|
|
105
|
+
|
|
106
|
+
### Dependencies
|
|
107
|
+
The `DEFAULT_VIEW` is typically a module-level function. Ensure all dependencies (enums, constants, other components) are imported at the top of the file so they are available in the function scope.
|
|
108
|
+
|
|
109
|
+
## Step-by-Step Implementation Example
|
|
110
|
+
|
|
111
|
+
### 1\. Create the Widget File and Styles
|
|
112
|
+
|
|
113
|
+
Example: `front_end/panels/my_example/MyExampleWidget.ts`.
|
|
114
|
+
|
|
115
|
+
```css
|
|
116
|
+
/* front_end/panels/my_example/myExampleWidget.css */
|
|
117
|
+
@scope to (devtools-widget > *) {
|
|
118
|
+
p {
|
|
119
|
+
color: blue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
// front_end/panels/my_example/MyExampleWidget.ts
|
|
126
|
+
|
|
127
|
+
import * as Lit from '../../ui/lit/lit.js';
|
|
128
|
+
import * as UI from '../../ui/legacy/legacy.js';
|
|
129
|
+
|
|
130
|
+
import myExampleWidgetStyles from './myExampleWidget.css.js';
|
|
131
|
+
|
|
132
|
+
const {html, render, ref} = Lit;
|
|
133
|
+
|
|
134
|
+
// 1. Define Input data shape. (Assuming Child Widget configuration for completeness)
|
|
135
|
+
export interface ViewInput {
|
|
136
|
+
title: string;
|
|
137
|
+
counter: number;
|
|
138
|
+
onButtonClick: () => void;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 2. Define Output imperative API shape.
|
|
142
|
+
export interface ViewOutput {
|
|
143
|
+
focusButton?: () => void;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 3. Define the View function.
|
|
147
|
+
// clang-format off
|
|
148
|
+
const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLElement): void => {
|
|
149
|
+
render(html`
|
|
150
|
+
<style>${myExampleWidgetStyles}</style>
|
|
151
|
+
<div class="child-widget">
|
|
152
|
+
<h3>${input.title}</h3>
|
|
153
|
+
<p>Counter: ${input.counter}</p>
|
|
154
|
+
<button @click=${input.onButtonClick} ${ref(el => {
|
|
155
|
+
if (el) {
|
|
156
|
+
output.focusButton = () => el.focus();
|
|
157
|
+
}
|
|
158
|
+
})}>Increment</button>
|
|
159
|
+
</div>
|
|
160
|
+
`, target);
|
|
161
|
+
};
|
|
162
|
+
// clang-format on
|
|
163
|
+
|
|
164
|
+
// 4. Type alias for the view.
|
|
165
|
+
type View = typeof DEFAULT_VIEW;
|
|
166
|
+
|
|
167
|
+
// 5. Define the Presenter.
|
|
168
|
+
export class MyExampleWidget extends UI.Widget.Widget {
|
|
169
|
+
#counter: number = 0;
|
|
170
|
+
|
|
171
|
+
// Public field REQUIRED to receive data from a parent.
|
|
172
|
+
title: string = 'Default Title';
|
|
173
|
+
|
|
174
|
+
#view: View;
|
|
175
|
+
#viewOutput: ViewOutput = {};
|
|
176
|
+
|
|
177
|
+
constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
|
|
178
|
+
super(element);
|
|
179
|
+
this.#view = view;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
override wasShown(): void {
|
|
183
|
+
super.wasShown();
|
|
184
|
+
// Initial render trigger.
|
|
185
|
+
this.requestUpdate();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
focus(): void {
|
|
189
|
+
this.#viewOutput.focusButton?.();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
#handleButtonClick = (): void => {
|
|
193
|
+
this.#counter++;
|
|
194
|
+
this.requestUpdate();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
override performUpdate(): void {
|
|
198
|
+
const viewInput = {
|
|
199
|
+
title: this.title,
|
|
200
|
+
counter: this.#counter,
|
|
201
|
+
onButtonClick: this.#handleButtonClick,
|
|
202
|
+
};
|
|
203
|
+
this.#view(viewInput, this.#viewOutput, this.contentElement);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 2\. Compose Widgets (Parent Widget)
|
|
209
|
+
|
|
210
|
+
Example: `front_end/panels/parent/ParentWidget.ts`. Demonstrates passing state via `widgetConfig` and a view with no `output`.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
// front_end/panels/parent/ParentWidget.ts
|
|
214
|
+
|
|
215
|
+
import * as Lit from '../../ui/lit/lit.js';
|
|
216
|
+
import * as UI from '../../ui/legacy/legacy.js';
|
|
217
|
+
import {MyExampleWidget} from '../my_example/MyExampleWidget.ts';
|
|
218
|
+
|
|
219
|
+
import parentWidgetStyles from './parentWidgetStyles.css';
|
|
220
|
+
|
|
221
|
+
const {html, render} = Lit;
|
|
222
|
+
const {widgetConfig} = UI.Widget;
|
|
223
|
+
|
|
224
|
+
interface ViewInput {
|
|
225
|
+
title: string;
|
|
226
|
+
onTitleChange: () => void;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Parent View
|
|
230
|
+
// clang-format off
|
|
231
|
+
const DEFAULT_VIEW = (input: ViewInput, output: undefined, target: HTMLElement): void => {
|
|
232
|
+
render(html`
|
|
233
|
+
<style>${parentWidgetStyles}</style>
|
|
234
|
+
<div class="parent-container">
|
|
235
|
+
<h1>Parent Widget</h1>
|
|
236
|
+
<button @click=${input.onTitleChange}>Change Child Title</button>
|
|
237
|
+
|
|
238
|
+
<!-- Pass properties to the child widget. -->
|
|
239
|
+
<devtools-widget .widgetConfig=${widgetConfig(MyExampleWidget, {title: input.title})}>
|
|
240
|
+
</devtools-widget>
|
|
241
|
+
</div>
|
|
242
|
+
`, target);
|
|
243
|
+
};
|
|
244
|
+
// clang-format on
|
|
245
|
+
|
|
246
|
+
type View = typeof DEFAULT_VIEW;
|
|
247
|
+
|
|
248
|
+
// Parent Presenter
|
|
249
|
+
export class ParentWidget extends UI.Widget.Widget {
|
|
250
|
+
#childTitle: string = 'Initial Title';
|
|
251
|
+
#view: View;
|
|
252
|
+
|
|
253
|
+
constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
|
|
254
|
+
super(element);
|
|
255
|
+
this.#view = view;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
override wasShown(): void {
|
|
259
|
+
super.wasShown();
|
|
260
|
+
this.requestUpdate();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#handleChangeTitleClick = () => {
|
|
264
|
+
this.#childTitle = `Title set at ${new Date().toLocaleTimeString()}`;
|
|
265
|
+
this.requestUpdate();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
override performUpdate(): void {
|
|
269
|
+
this.#view({
|
|
270
|
+
title: this.#childTitle,
|
|
271
|
+
onTitleChange: this.#handleChangeTitleClick,
|
|
272
|
+
}, undefined, this.contentElement);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### 3\. Testing Widgets
|
|
278
|
+
|
|
279
|
+
Testing focuses on the Presenter's logic. The View MUST be replaced by a stub using `createViewFunctionStub`.
|
|
280
|
+
|
|
281
|
+
* **`describeWithEnvironment`**: Sets up the testing environment.
|
|
282
|
+
* **`createViewFunctionStub`**: Stubs the view to capture `input` data.
|
|
283
|
+
* **`renderElementIntoDOM`**: Attaches the widget to the DOM to trigger lifecycle methods (like `wasShown`).
|
|
284
|
+
* **`view.nextInput`**: A promise that resolves with the next `input` when the widget re-renders.
|
|
285
|
+
* **Simulating Events**: Invoke callbacks directly (e.g., `view.input.onButtonClick()`), do not interact with the DOM.
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
// front_end/panels/my_example/MyExampleWidget.test.ts
|
|
289
|
+
|
|
290
|
+
import {
|
|
291
|
+
renderElementIntoDOM,
|
|
292
|
+
} from '../../testing/DOMHelpers.js';
|
|
293
|
+
import {describeWithEnvironment} from '../../testing/EnvironmentHelpers.js';
|
|
294
|
+
import {createViewFunctionStub} from '../../testing/ViewFunctionHelpers.js';
|
|
295
|
+
import * as UI from '../../ui/legacy/legacy.js';
|
|
296
|
+
|
|
297
|
+
import * as MyExampleWidget from './MyExampleWidget.js';
|
|
298
|
+
|
|
299
|
+
const {MyExampleWidget} = MyExampleWidget;
|
|
300
|
+
|
|
301
|
+
describeWithEnvironment('MyExampleWidget', () => {
|
|
302
|
+
// Helper to set up the widget with a stubbed view.
|
|
303
|
+
async function createWidget() {
|
|
304
|
+
// 1. Stub the view function.
|
|
305
|
+
const view = createViewFunctionStub(MyExampleWidget);
|
|
306
|
+
|
|
307
|
+
// 2. Instantiate the widget, injecting the stub.
|
|
308
|
+
const widget = new MyExampleWidget(undefined, view);
|
|
309
|
+
|
|
310
|
+
// 3. Render the widget into the DOM (triggers `wasShown`).
|
|
311
|
+
widget.markAsRoot();
|
|
312
|
+
renderElementIntoDOM(widget);
|
|
313
|
+
|
|
314
|
+
// 4. Wait for the initial render.
|
|
315
|
+
await view.nextInput;
|
|
316
|
+
|
|
317
|
+
return {view, widget};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
it('renders with initial state', async () => {
|
|
321
|
+
const {view} = await createWidget();
|
|
322
|
+
assert.strictEqual(view.input.counter, 0);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('increments counter on button click', async () => {
|
|
326
|
+
const {view} = await createWidget();
|
|
327
|
+
|
|
328
|
+
// Simulate the click by invoking the callback.
|
|
329
|
+
view.input.onButtonClick();
|
|
330
|
+
|
|
331
|
+
// Wait for the re-render.
|
|
332
|
+
const finalInput = await view.nextInput;
|
|
333
|
+
|
|
334
|
+
assert.strictEqual(finalInput.counter, 1);
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('updates its title when a new one is passed in', async () => {
|
|
338
|
+
const {view, widget} = await createWidget();
|
|
339
|
+
|
|
340
|
+
widget.title = 'New Test Title';
|
|
341
|
+
widget.requestUpdate();
|
|
342
|
+
|
|
343
|
+
const finalInput = await view.nextInput;
|
|
344
|
+
assert.strictEqual(finalInput.title, 'New Test Title');
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Improving this Guide
|
|
350
|
+
|
|
351
|
+
This document is a living guide. If you find that the instructions are incomplete, lead to errors, or could be improved, please suggest updates. Your goal is to make this guide as helpful as possible for both human developers and your future AI counterparts.
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
## Linting
|
|
19
19
|
|
|
20
|
-
- `npm run lint` will execute ESLint and StyleLint
|
|
20
|
+
- `npm run lint` will execute ESLint and StyleLint. It will report any violations and automatically fix them where possible.
|
|
21
|
+
- To run the linter on a specific file or directory, you can run `npm run lint -- <PATH>` where `PATH` is a path to a file or directory. This will also automatically fix violations where possible.
|
|
21
22
|
|
|
22
23
|
## Best practices
|
|
23
24
|
|
|
@@ -6,12 +6,11 @@ This page assumes a working Chromium DevTools [checkout and build](../get_the_co
|
|
|
6
6
|
|
|
7
7
|
1. [Quick Start](#Quick-Start)
|
|
8
8
|
2. [IDE Setup](#IDE-Setup)
|
|
9
|
-
3. [
|
|
10
|
-
4. [
|
|
11
|
-
5. [
|
|
12
|
-
6. [
|
|
13
|
-
7. [
|
|
14
|
-
8. [Legal Stuff](#Legal-Stuff)
|
|
9
|
+
3. [Contributing changes](./changes.md)
|
|
10
|
+
4. [Issues Guidelines](./issues.md)
|
|
11
|
+
5. [Settings, Experiments, and Features](./settings-experiments-features.md)
|
|
12
|
+
6. [Infrastructure](./infrastructure.md)
|
|
13
|
+
7. [Legal Stuff](#Legal-Stuff)
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
## Quick Start
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Contributing changes to Chromium DevTools
|
|
2
2
|
|
|
3
3
|
See [Get the Code](../get_the_code.md) for details on how to checkout the code,
|
|
4
|
-
and
|
|
5
|
-
Also check out [Contributing to
|
|
4
|
+
and check out [Contributing to
|
|
6
5
|
Chromium](https://chromium.googlesource.com/chromium/src/+/main/docs/contributing.md)
|
|
7
6
|
for general information how to contribute to any Chromium project (including
|
|
8
7
|
its developer tools).
|
|
@@ -16,6 +16,6 @@ user interface.
|
|
|
16
16
|
|
|
17
17
|
## UI reviews
|
|
18
18
|
If you make changes to DevTools UI, make sure to involve us **early**, i.e. already in the
|
|
19
|
-
UI design stage.
|
|
19
|
+
UI design stage.
|
|
20
20
|
|
|
21
21
|
Back to the [Chromium DevTools Documentation](../../README.md).
|
|
@@ -792,10 +792,12 @@ export class OverlayModel extends SDKModel<EventTypes> implements ProtocolProxyA
|
|
|
792
792
|
return await this.#windowControls.initializeStyleSheetText(url);
|
|
793
793
|
}
|
|
794
794
|
|
|
795
|
-
inspectPanelShowRequested(
|
|
795
|
+
inspectPanelShowRequested({backendNodeId}: Protocol.Overlay.InspectPanelShowRequestedEvent): void {
|
|
796
|
+
this.dispatchEventToListeners(Events.INSPECT_PANEL_SHOW_REQUESTED, backendNodeId);
|
|
796
797
|
}
|
|
797
798
|
|
|
798
|
-
inspectedElementWindowRestored(
|
|
799
|
+
inspectedElementWindowRestored({backendNodeId}: Protocol.Overlay.InspectedElementWindowRestoredEvent): void {
|
|
800
|
+
this.dispatchEventToListeners(Events.INSPECTED_ELEMENT_WINDOW_RESTORED, backendNodeId);
|
|
799
801
|
}
|
|
800
802
|
}
|
|
801
803
|
|
|
@@ -66,6 +66,11 @@ export abstract class RemoteObject {
|
|
|
66
66
|
return matches ? parseInt(matches[1], 10) : 0;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
static isEmptyArray(object: RemoteObject|Protocol.Runtime.RemoteObject|Protocol.Runtime.ObjectPreview): boolean {
|
|
70
|
+
const matches = object.description?.match(descriptionLengthParenRegex);
|
|
71
|
+
return Boolean(matches?.[1] === '0');
|
|
72
|
+
}
|
|
73
|
+
|
|
69
74
|
static unserializableDescription(object: unknown): string|null {
|
|
70
75
|
if (typeof object === 'number') {
|
|
71
76
|
const description = String(object);
|
|
@@ -582,7 +587,8 @@ export class RemoteObjectImpl extends RemoteObject {
|
|
|
582
587
|
|
|
583
588
|
override isLinearMemoryInspectable(): boolean {
|
|
584
589
|
return this.type === 'object' && this.subtype !== undefined &&
|
|
585
|
-
['webassemblymemory', 'typedarray', 'dataview', 'arraybuffer'].includes(this.subtype)
|
|
590
|
+
['webassemblymemory', 'typedarray', 'dataview', 'arraybuffer'].includes(this.subtype) &&
|
|
591
|
+
!RemoteObject.isEmptyArray(this);
|
|
586
592
|
}
|
|
587
593
|
}
|
|
588
594
|
|
|
@@ -56,7 +56,12 @@ export function parseStorageKey(storageKeyString: string): StorageKey {
|
|
|
56
56
|
// third_party/blink/common/storage_key/storage_key.cc
|
|
57
57
|
const components = storageKeyString.split('^');
|
|
58
58
|
const origin = Common.ParsedURL.ParsedURL.extractOrigin(components[0] as Platform.DevToolsPath.UrlString);
|
|
59
|
-
const storageKey = {
|
|
59
|
+
const storageKey = {
|
|
60
|
+
// For file:// URLs, extracting the origin collapses it to "file://".
|
|
61
|
+
// Node.js uses the full file URL as the StorageKey, so keep the original URL here.
|
|
62
|
+
origin: origin === 'file://' ? components[0] as Platform.DevToolsPath.UrlString : origin,
|
|
63
|
+
components: new Map<StorageKeyComponent, string>()
|
|
64
|
+
};
|
|
60
65
|
for (let i = 1; i < components.length; ++i) {
|
|
61
66
|
storageKey.components.set(components[i].charAt(0) as StorageKeyComponent, components[i].substring(1));
|
|
62
67
|
}
|
|
@@ -49,7 +49,7 @@ export class Target extends ProtocolClient.InspectorBackend.TargetBase {
|
|
|
49
49
|
this.#capabilitiesMask = Capability.BROWSER | Capability.STORAGE | Capability.DOM | Capability.JS |
|
|
50
50
|
Capability.LOG | Capability.NETWORK | Capability.TARGET | Capability.TRACING | Capability.EMULATION |
|
|
51
51
|
Capability.INPUT | Capability.INSPECTOR | Capability.AUDITS | Capability.WEB_AUTHN | Capability.IO |
|
|
52
|
-
Capability.MEDIA | Capability.EVENT_BREAKPOINTS;
|
|
52
|
+
Capability.MEDIA | Capability.EVENT_BREAKPOINTS | Capability.DOM_STORAGE;
|
|
53
53
|
if (parentTarget?.type() !== Type.FRAME) {
|
|
54
54
|
// This matches backend exposing certain capabilities only for the main frame.
|
|
55
55
|
this.#capabilitiesMask |=
|
|
@@ -92,7 +92,8 @@ export class Target extends ProtocolClient.InspectorBackend.TargetBase {
|
|
|
92
92
|
this.#capabilitiesMask = Capability.JS | Capability.LOG | Capability.EVENT_BREAKPOINTS | Capability.NETWORK;
|
|
93
93
|
break;
|
|
94
94
|
case Type.NODE:
|
|
95
|
-
this.#capabilitiesMask =
|
|
95
|
+
this.#capabilitiesMask =
|
|
96
|
+
Capability.JS | Capability.NETWORK | Capability.TARGET | Capability.IO | Capability.DOM_STORAGE;
|
|
96
97
|
break;
|
|
97
98
|
case Type.AUCTION_WORKLET:
|
|
98
99
|
this.#capabilitiesMask = Capability.JS | Capability.EVENT_BREAKPOINTS;
|
|
@@ -318,5 +319,6 @@ export const enum Capability {
|
|
|
318
319
|
IO = 1 << 17,
|
|
319
320
|
MEDIA = 1 << 18,
|
|
320
321
|
EVENT_BREAKPOINTS = 1 << 19,
|
|
322
|
+
DOM_STORAGE = 1 << 20,
|
|
321
323
|
NONE = 0,
|
|
322
324
|
}
|
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
</style>
|
|
17
|
-
<meta
|
|
17
|
+
<meta
|
|
18
|
+
http-equiv="Content-Security-Policy"
|
|
19
|
+
content="default-src 'self' devtools: data:; style-src 'self' 'unsafe-inline' devtools:; object-src 'none'; script-src
|
|
20
|
+
'self' https://chrome-devtools-frontend.appspot.com; img-src 'self' data:; frame-src * data:; connect-src data:
|
|
21
|
+
https://chromeuxreport.googleapis.com 'self' devtools:;">
|
|
18
22
|
<meta name="referrer" content="no-referrer">
|
|
19
23
|
<script type="module" src="./entrypoints/%ENTRYPOINT_NAME%/%ENTRYPOINT_NAME%.js"></script>
|
|
20
24
|
<link href="./application_tokens.css" rel="stylesheet">
|