chrome-devtools-frontend 1.0.1544076 → 1.0.1547147

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.
Files changed (178) hide show
  1. package/config/typescript/tsconfig.eslint.json +1 -0
  2. package/docs/styleguide/ux/styles.md +1 -1
  3. package/eslint.config.mjs +1 -1
  4. package/front_end/Images/src/arrow-down.svg +8 -1
  5. package/front_end/Images/src/arrow-up.svg +8 -1
  6. package/front_end/core/common/ParsedURL.ts +1 -1
  7. package/front_end/core/common/common.ts +0 -2
  8. package/front_end/core/host/AidaClient.ts +1 -1
  9. package/front_end/core/host/InspectorFrontendHostAPI.ts +0 -1
  10. package/front_end/core/host/UserMetrics.ts +0 -5
  11. package/front_end/core/platform/HostRuntime.ts +18 -0
  12. package/front_end/core/platform/KeyboardUtilities.ts +2 -2
  13. package/front_end/core/platform/StringUtilities.ts +1 -1
  14. package/front_end/core/platform/api/HostRuntime.ts +20 -0
  15. package/front_end/core/platform/api/api.ts +7 -0
  16. package/front_end/core/platform/browser/HostRuntime.ts +14 -0
  17. package/front_end/core/platform/browser/browser.ts +7 -0
  18. package/front_end/core/platform/node/HostRuntime.ts +13 -0
  19. package/front_end/core/platform/node/node.ts +7 -0
  20. package/front_end/core/platform/platform.ts +2 -2
  21. package/front_end/core/protocol_client/CDPConnection.ts +3 -3
  22. package/front_end/core/protocol_client/DevToolsCDPConnection.ts +2 -1
  23. package/front_end/core/sdk/CSSMetadata.ts +17 -5
  24. package/front_end/core/sdk/NetworkManager.ts +6 -8
  25. package/front_end/core/sdk/NetworkRequest.ts +4 -0
  26. package/front_end/core/sdk/SDKModel.ts +4 -2
  27. package/front_end/core/sdk/SourceMapScopesInfo.ts +141 -23
  28. package/front_end/core/sdk/Target.ts +5 -14
  29. package/front_end/core/sdk/TargetManager.ts +39 -18
  30. package/front_end/core/sdk/sdk-meta.ts +62 -0
  31. package/front_end/devtools_compatibility.js +0 -1
  32. package/front_end/entrypoints/main/MainImpl.ts +2 -2
  33. package/front_end/foundation/Universe.ts +2 -2
  34. package/front_end/generated/Deprecation.ts +11 -0
  35. package/front_end/generated/InspectorBackendCommands.ts +3 -6
  36. package/front_end/generated/SupportedCSSProperties.js +4 -25
  37. package/front_end/generated/protocol-mapping.d.ts +0 -15
  38. package/front_end/generated/protocol-proxy-api.d.ts +0 -11
  39. package/front_end/generated/protocol.ts +5 -36
  40. package/front_end/models/ai_assistance/AiConversation.ts +188 -0
  41. package/front_end/models/ai_assistance/AiHistoryStorage.ts +1 -172
  42. package/front_end/models/ai_assistance/ConversationHandler.ts +5 -5
  43. package/front_end/models/ai_assistance/agents/AiAgent.ts +1 -3
  44. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +6 -2
  45. package/front_end/models/ai_assistance/agents/StylingAgent.snapshot.txt +1 -1
  46. package/front_end/models/ai_assistance/agents/StylingAgent.ts +3 -9
  47. package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
  48. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +313 -313
  49. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +8 -6
  50. package/front_end/models/ai_assistance/performance/AICallTree.snapshot.txt +33 -33
  51. package/front_end/models/ai_assistance/performance/AICallTree.ts +9 -3
  52. package/front_end/models/bindings/CSSWorkspaceBinding.ts +5 -3
  53. package/front_end/models/bindings/SASSSourceMapping.ts +6 -4
  54. package/front_end/models/cpu_profile/CPUProfileDataModel.ts +10 -7
  55. package/front_end/models/crux-manager/CrUXManager.ts +7 -4
  56. package/front_end/models/issues_manager/GenericIssue.ts +12 -9
  57. package/front_end/models/javascript_metadata/NativeFunctions.js +4 -0
  58. package/front_end/models/trace/handlers/SamplesHandler.ts +3 -0
  59. package/front_end/models/trace/helpers/Trace.ts +13 -0
  60. package/front_end/models/trace/types/TraceEvents.ts +2 -1
  61. package/front_end/models/trace_source_maps_resolver/SourceMapsResolver.ts +29 -0
  62. package/front_end/models/workspace/IgnoreListManager.ts +1 -2
  63. package/front_end/models/workspace/UISourceCode.ts +50 -0
  64. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +11 -10
  65. package/front_end/panels/ai_assistance/ai_assistance-meta.ts +8 -0
  66. package/front_end/panels/ai_assistance/components/ChatView.ts +2 -2
  67. package/front_end/panels/ai_assistance/components/UserActionRow.ts +2 -1
  68. package/front_end/panels/animation/AnimationTimeline.ts +0 -8
  69. package/front_end/panels/application/ApplicationPanelSidebar.ts +6 -7
  70. package/front_end/panels/application/{components/FrameDetailsView.ts → FrameDetailsView.ts} +140 -171
  71. package/front_end/panels/application/{components/OriginTrialTreeView.ts → OriginTrialTreeView.ts} +9 -9
  72. package/front_end/panels/application/application.ts +4 -0
  73. package/front_end/panels/application/components/StackTrace.ts +89 -88
  74. package/front_end/panels/application/components/components.ts +2 -4
  75. package/front_end/panels/application/{components/frameDetailsReportView.css → frameDetailsReportView.css} +5 -1
  76. package/front_end/panels/common/AiCodeGenerationTeaser.ts +80 -0
  77. package/front_end/panels/common/BadgeNotification.ts +2 -1
  78. package/front_end/panels/common/DOMLinkifier.ts +7 -2
  79. package/front_end/panels/common/GdpSignUpDialog.ts +2 -1
  80. package/front_end/panels/common/common.ts +2 -1
  81. package/front_end/panels/console/ConsolePrompt.ts +3 -1
  82. package/front_end/panels/console/ConsoleViewport.ts +1 -2
  83. package/front_end/panels/elements/ElementIssueUtils.ts +2 -2
  84. package/front_end/panels/elements/ElementStatePaneWidget.ts +2 -1
  85. package/front_end/panels/elements/StylePropertiesSection.ts +1 -1
  86. package/front_end/panels/elements/StylePropertyTreeElement.ts +23 -19
  87. package/front_end/panels/elements/StylesSidebarPane.ts +1 -1
  88. package/front_end/panels/elements/cssValueTraceView.css +1 -1
  89. package/front_end/panels/elements/elements-meta.ts +1 -22
  90. package/front_end/panels/explain/components/ConsoleInsight.ts +44 -57
  91. package/front_end/panels/explain/components/consoleInsight.css +46 -1
  92. package/front_end/panels/layer_viewer/LayerTreeOutline.ts +1 -2
  93. package/front_end/panels/lighthouse/LighthouseProtocolService.ts +3 -6
  94. package/front_end/panels/mobile_throttling/NetworkThrottlingSelector.ts +19 -0
  95. package/front_end/panels/network/RequestConditionsDrawer.ts +54 -24
  96. package/front_end/panels/network/networkLogView.css +11 -0
  97. package/front_end/panels/network/networkTimingTable.css +8 -6
  98. package/front_end/panels/network/requestConditionsDrawer.css +10 -1
  99. package/front_end/panels/profiler/ProfilesPanel.ts +1 -2
  100. package/front_end/panels/settings/FrameworkIgnoreListSettingsTab.ts +2 -1
  101. package/front_end/panels/settings/KeybindsSettingsTab.ts +20 -21
  102. package/front_end/panels/settings/SettingsScreen.ts +3 -2
  103. package/front_end/panels/sources/CoveragePlugin.ts +5 -5
  104. package/front_end/panels/sources/Plugin.ts +1 -1
  105. package/front_end/panels/sources/ProfilePlugin.ts +22 -14
  106. package/front_end/panels/sources/UISourceCodeFrame.ts +2 -1
  107. package/front_end/panels/sources/sources-meta.ts +0 -62
  108. package/front_end/panels/timeline/README.md +1 -9
  109. package/front_end/panels/timeline/ThreadAppender.ts +0 -7
  110. package/front_end/panels/timeline/TimelinePanel.ts +1 -1
  111. package/front_end/panels/timeline/TimelineUIUtils.ts +2 -0
  112. package/front_end/panels/timeline/components/ExportTraceOptions.ts +15 -1
  113. package/front_end/panels/timeline/components/LiveMetricsView.ts +51 -6
  114. package/front_end/panels/timeline/components/MetricCard.ts +2 -2
  115. package/front_end/panels/timeline/components/exportTraceOptions.css +11 -2
  116. package/front_end/panels/timeline/components/insights/NodeLink.ts +2 -3
  117. package/front_end/panels/timeline/overlays/components/EntryLabelOverlay.ts +2 -1
  118. package/front_end/panels/timeline/timeline-meta.ts +0 -10
  119. package/front_end/panels/timeline/timeline.ts +0 -2
  120. package/front_end/panels/whats_new/ReleaseNoteView.ts +2 -1
  121. package/front_end/panels/whats_new/WhatsNewImpl.ts +3 -2
  122. package/front_end/third_party/chromium/README.chromium +1 -1
  123. package/front_end/tsconfig.json +1 -0
  124. package/front_end/ui/components/buttons/Button.docs.ts +6 -5
  125. package/front_end/ui/components/markdown_view/MarkdownLinksMap.ts +1 -0
  126. package/front_end/ui/components/snackbars/Snackbars.docs.ts +1 -1
  127. package/front_end/ui/components/spinners/Spinners.docs.ts +1 -1
  128. package/front_end/ui/components/survey_link/SurveyLink.docs.ts +2 -1
  129. package/front_end/ui/components/switch/Switch.docs.ts +1 -1
  130. package/front_end/ui/components/tooltips/Tooltip.docs.ts +3 -3
  131. package/front_end/ui/helpers/OpenInNewTab.ts +87 -0
  132. package/front_end/ui/helpers/helpers.ts +5 -0
  133. package/front_end/ui/legacy/ARIAUtils.ts +2 -2
  134. package/front_end/ui/legacy/ActionRegistration.ts +11 -0
  135. package/front_end/ui/legacy/ContextMenu.docs.ts +12 -11
  136. package/front_end/ui/legacy/RadioButton.docs.ts +1 -1
  137. package/front_end/ui/legacy/SelectMenu.docs.ts +1 -1
  138. package/front_end/ui/legacy/Slider.docs.ts +1 -1
  139. package/front_end/ui/legacy/SoftDropDown.ts +2 -2
  140. package/front_end/ui/legacy/TextPrompt.ts +3 -2
  141. package/front_end/ui/legacy/Treeoutline.ts +2 -1
  142. package/front_end/ui/legacy/UIUtils.ts +11 -43
  143. package/front_end/ui/legacy/Widget.ts +3 -2
  144. package/front_end/ui/legacy/XLink.ts +4 -4
  145. package/front_end/ui/legacy/components/color_picker/ContrastDetails.ts +2 -1
  146. package/front_end/ui/legacy/components/data_grid/DataGrid.ts +2 -2
  147. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +144 -143
  148. package/front_end/ui/legacy/components/perf_ui/LineLevelProfile.ts +62 -39
  149. package/front_end/ui/legacy/components/perf_ui/OverviewGrid.ts +1 -1
  150. package/front_end/ui/legacy/components/perf_ui/TimelineGrid.ts +2 -2
  151. package/front_end/ui/legacy/components/source_frame/SourceFrame.ts +2 -7
  152. package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +1 -2
  153. package/front_end/ui/legacy/components/utils/Linkifier.ts +2 -1
  154. package/front_end/ui/legacy/inspectorCommon.css +2 -2
  155. package/front_end/ui/legacy/legacy.ts +2 -0
  156. package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
  157. package/mcp/tsconfig.json +16 -0
  158. package/package.json +2 -2
  159. package/front_end/core/common/Linkifier.ts +0 -55
  160. package/front_end/panels/explain/components/consoleInsightSourcesList.css +0 -51
  161. package/front_end/panels/timeline/CLSLinkifier.ts +0 -58
  162. package/front_end/ui/components/docs/README.md +0 -6
  163. package/front_end/ui/components/docs/building-ui-documentation/ComponentEvents.md +0 -54
  164. package/front_end/ui/components/docs/building-ui-documentation/ComponentPerformance.md +0 -136
  165. package/front_end/ui/components/docs/building-ui-documentation/CreatingComponents.md +0 -242
  166. package/front_end/ui/components/docs/building-ui-documentation/README.md +0 -23
  167. package/front_end/ui/components/docs/building-ui-documentation/StylingComponents.md +0 -66
  168. package/front_end/ui/components/docs/building-ui-documentation/TestingComponents.md +0 -111
  169. package/front_end/ui/components/docs/component_docs.ts +0 -24
  170. package/front_end/ui/components/docs/component_docs_styles.css +0 -53
  171. package/front_end/ui/components/docs/create_breadcrumbs.ts +0 -44
  172. package/front_end/ui/components/docs/slider/basic.html +0 -20
  173. package/front_end/ui/components/docs/switch/basic.html +0 -20
  174. /package/front_end/models/issues_manager/descriptions/{genericFormAriaLabelledByToNonExistingId.md → genericFormAriaLabelledByToNonExistingIdError.md} +0 -0
  175. /package/front_end/models/issues_manager/descriptions/{genericFormLabelHasNeitherForNorNestedInput.md → genericFormLabelHasNeitherForNorNestedInputError.md} +0 -0
  176. /package/front_end/panels/application/{components/originTrialTokenRows.css → originTrialTokenRows.css} +0 -0
  177. /package/front_end/panels/application/{components/originTrialTreeView.css → originTrialTreeView.css} +0 -0
  178. /package/front_end/{core/platform → ui/legacy}/DOMUtilities.ts +0 -0
@@ -1,136 +0,0 @@
1
- # Performance of Components
2
-
3
- Because we call the `#render()` method of a component frequently whenever any data changes, it is possible to create very inefficient component render calls that can lead to a poor experience for users.
4
-
5
- If the `#render()` method is expensive to run, and the component's data frequently changes, we will deliver a janky experience to users. This document lists some common causes of issues and ways to mitigate or avoid them. Most of these come from real world instances on DevTools; please feel free to add any that you come across!
6
-
7
- ## Manually creating component instances
8
-
9
- When you re-render a component, Lit is smart and does the minimal amount of work. For example, given a `#render()` method that looks like this:
10
-
11
- ```
12
- return html`<devtools-foo .name=${name}></devtools-foo>`
13
- ```
14
-
15
- Lit knows that the only dynamic part of that template is the `name` property, and as such on each re-render it only updates that part (and only if it has changed). It will not recreate `devtools-foo`.
16
-
17
- > You can read more about this in the [Lit templates documentation](https://lit.dev/docs/templates/overview/).
18
-
19
- However, if we change the `#render()` method to:
20
-
21
- ```
22
- const foo = new DevToolsFoo();
23
- foo.name = name;
24
- return html`${foo}`
25
- ```
26
-
27
- Now, we wil recreate `foo` on each re-render. This is not only wasted effort, but can mess with the user's experience if they had focused the node, only to have it destroyed and recreated.
28
-
29
- You should always let Lit manage instances of components. If you cannot do this, you should ensure some layer of caching to make sure instnaces are re-used whenver possible. However, **be careful!** It is very hard to implement a reliable cache for elements, and should be done only as a last resort.
30
-
31
- ## Integrating DevTools widgets & Lit and causing re-renders
32
-
33
- This issue is similar to the one above; often we can hit performance issues when using Lit to render a DevTools widget that we want to use. In a perfect world we would always re-build these widgets into Lit components, but that is often a huge undertaking and one that is not justifiable. The design of the widgets system is that they are only expected to be created once, and not re-evaluated. They expect to have their `update()` method called to trigger an update.
34
-
35
- In this instance, you will need to:
36
-
37
- 1. Manage the instance(s) of the widget, and ensure that we are not destroying and recreating the widget on each render.
38
- 2. Ensure the `update()` method of the widget is called when the relevant data changes (or on each `#render()` call).
39
-
40
- For example, this is likely to cause issues:
41
-
42
- ```ts
43
- // within #render()
44
- const someWidget = new SomeWidget();
45
- return html`<div>${someWidget.element}</div>`
46
- ```
47
-
48
- Because the widget will be created on each render. Instead, store the widget and `update()` it:
49
-
50
- ```ts
51
- // within the component
52
- #widgetInstance = new SomeWidget();
53
-
54
- // within #render()
55
- this.#widgetInstance.update();
56
- return html`<div>${this.#widgetInstance.element}</div>`;
57
- ```
58
-
59
- ## Swap from `set data` to individual properties
60
-
61
- When introducing lit-html into the DevTools codebase we made the decision to prefer:
62
-
63
- ```ts
64
- set data(data: FooData) {
65
- this.#x = data.x;
66
- this.#y = data.y;
67
- // And so on
68
- }
69
- ```
70
-
71
- Rather than:
72
-
73
- ```ts
74
- set x(x: number) {
75
- this.#x = x;
76
- }
77
-
78
- set y(y: number) {
79
- this.#y = y;
80
- }
81
- ```
82
-
83
- We did this to avoid having to immediately implement batched rendering.
84
-
85
- However, this decision came with a performance penalty. Lit will only trigger a re-render of a component if one of its properties changes. But when we pass in a new object on each render, we are effectively triggering a re-render every time. Normally this impact isn't noticable, and most components don't re-render with the frequency required to cause performance issues. When a component that takes an object via `set data()` is frequently re-rendered, that causes a non-negligible amount of garbage collection to tidy up the one-use objects we create.
86
-
87
- Therefore, if a component is re-rendered often and you are seeing issues, you can switch it to:
88
-
89
- ```
90
- <devtools-foo .x=${x} .y=${y}></devtools-foo>
91
- ```
92
-
93
- If you take this approach, ensure each property setter uses the render scheduler to batch updates. Otherwise we trigger N renders, one per property, rather than 1.
94
-
95
- ```ts
96
- set x(x: number) {
97
- this.#x = x;
98
- void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
99
- }
100
- ```
101
-
102
- ## Passing callback functions that are recreated on each render
103
-
104
- A general theme of performance in frontend is to avoid re-renders whenever possible. When a property passed into a component changes, that will trigger a re-render (as in our components we schedule renders in any setters). One pattern that can cause many re-renders is passing functions into components.
105
-
106
- For example, imagine this `#render()` method:
107
-
108
- ```ts
109
- const onClick = function(event: Event) {
110
- // do something
111
- }
112
-
113
- return html`<devtools-foo .onClick=${onClick}></devtools-foo>`
114
- ```
115
-
116
- And within `devtools-foo`, we have:
117
-
118
- ```
119
- set onClick(func: (event: Event) => void) {
120
- if (this.#onClick === func) {
121
- return;
122
- }
123
- this.#onClick = func;
124
- void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
125
- }
126
- ```
127
-
128
- On each `#render()` call, `onClick` is recreated, so it is considered to be a new function, and thus we trigger a re-render. We could fix this by making `onClick` stable, and not re-creating it on each render, but a better way is to lean into the browser and use events.
129
-
130
- In this example, we would update `devtools-foo` to dispatch an event when the user performs an action. We could then update our `#render()` method to:
131
-
132
- ```ts
133
- return html`<devtools-foo @click=${this.#onClick}></devtools-foo>`
134
- ```
135
-
136
- Lit will manage this event listener and ensure that it is not needlessly unbound and rebound. Now, we don't cause `devtools-foo` to re-render when really nothing is changing.
@@ -1,242 +0,0 @@
1
- # Creating components
2
-
3
- A component is created by extending `HTMLElement`. The name of the component should match the filename. The component should be exported by the file.
4
-
5
- ```ts
6
- // ElementBreadcrumbs.ts
7
- export class ElementBreadcrumbs extends HTMLElement {
8
- }
9
- ```
10
-
11
- ## Where to put components
12
-
13
- If the component is for a specific panel, and not expected to be re-usable, it should be created within the panel's folder, within a sub-directory of `components`:
14
-
15
- ```
16
- front_end/panels/elements/components/ElementsBreadcrumbs.ts
17
- ```
18
-
19
- If a component is designed to be re-usable, it should live in `front_end/ui/components`, in its own folder. That folder also contains an entrypoint, along with files for the component's definition.
20
-
21
- ```
22
- front_end/ui/components/button/button.ts // entrypoint
23
- front_end/ui/components/button/Button.ts // component definition
24
- ```
25
-
26
- ## Defining and naming a component
27
-
28
- All custom elements **must** contain a hyphen. Use `devtools-` as the prefix. Use `customElements.define` to define the component and register it with the browser:
29
-
30
- ```ts
31
- customElements.define('devtools-elements-breadcrumbs', ElementsBreadcrumbs);
32
- ```
33
-
34
- And finally we tell TypeScript that this component exists:
35
-
36
- ```ts
37
- declare global {
38
- interface HTMLElementTagNameMap {
39
- 'devtools-elements-breadcrumbs': ElementsBreadcrumbs;
40
- }
41
- }
42
- ```
43
-
44
- By doing this, TypeScript understands that `document.querySelector('devtools-elements-breadcrumbs')` returns an `ElementsBreadcrumbs` instance.
45
-
46
- We use lit-analyzer in a lint state to verify that component definitions are imported where the component is used.
47
-
48
- ## Creating a shadow root
49
-
50
- Each component gets its own Shadow Root to ensure that styles and events that occur within it are encapsulated and do not leak out:
51
-
52
- ```ts
53
- export class ElementsBreadcrumbs extends HTMLElement {
54
- readonly #shadow = this.attachShadow({mode: 'open'});
55
- }
56
- ```
57
-
58
- > We set the mode to `open` so it's open to inspection via DevTools. [See this MDN explainer](https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/mode) for more details.
59
-
60
- ## Rendering a component
61
-
62
- Each component defines a `#render` method which is responsible for invoking LitHtml and having the component render HTML into the DOM.
63
-
64
- > The `#` symbol indicates a [private class method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields).
65
-
66
- The `#render` method calls `LitHtml.render`, building up a template with `LitHtml.html`:
67
-
68
- ```ts
69
- LitHtml.render(html`<p>hello world!</p>`, this.#shadow, {host: this});
70
- ```
71
-
72
- The third argument (`{host: this}`) tells LitHtml to automatically bind event listeners to the component. This can save you some painful debugging where event listeners do not have the right `this` reference; so we enforce the use of `{host: this}` via a custom ESLint rule.
73
-
74
- There is unfortunately a [clang-format bug](crbug.com/1079231) which makes its auto-formatting of LitHtml templates very unreadable, so we usually disable clang-format round the call:
75
-
76
- ```ts
77
- // clang-format off
78
- LitHtml.render(...)
79
- // clang-format on
80
- ```
81
-
82
- ## Scheduled and batched rendering
83
-
84
- To have your component render, you could manually call `this.#render()`. However, if you were to have multiple updates to a component (perhaps some values it's passed get changed), we want to avoid having multiple renders where possible and instead batch them.
85
-
86
- We can use the `ScheduledRender` helper (`front_end/ui/components/helpers/scheduled-render.ts`) to achieve this. Rather than call `this.#render()` directly, you instead call the scheduler:
87
-
88
- ```ts
89
- void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
90
- ```
91
-
92
- > `scheduleRender` returns a promise; we use the `void` keyword to instruct TypeScript that we are purposefully not using `await` to wait for the promise to resolve. When scheduling a render it's most common to "fire and forget".
93
-
94
- To summarise, most components start off life looking like:
95
-
96
- ```ts
97
- export class ElementsBreadcrumbs extends HTMLElement {
98
- readonly #shadow = this.attachShadow({mode: 'open'});
99
-
100
- #render(): void {
101
- LitHtml.render(html``, this.#shadow, {host:this});
102
- }
103
- }
104
- ```
105
-
106
- ## Triggering a render
107
-
108
- One of the most important aspects to understand about our component system is that **rendering does not happen automatically**.
109
-
110
- To ensure we trigger a render once the component is added to the DOM, we can define [`connectedCallback`](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#:~:text=lifecycle.%20For%20example%2C-,connectedCallback,-is%20invoked%20each):
111
-
112
- ```ts
113
- export class ElementsBreadcrumbs extends HTMLElement {
114
- readonly #shadow = this.attachShadow({mode: 'open'});
115
-
116
- connectedCallback(): void {
117
- void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
118
- }
119
-
120
- #render(): void {
121
- LitHtml.render(html``, this.#shadow, {host:this});
122
- }
123
- }
124
- ```
125
-
126
- ## Passing data into a component
127
-
128
- Most of our components will require data that is passed into them. For example, `ElementsBreadcrumbs` takes in an array of `DOMNode` objects.
129
-
130
- To provide a component some data, we define a `data` setter. This setter takes an object with any data the component requires. This object should have a TypeScript interface defined for it:
131
-
132
- ```ts
133
- export interface ElementsBreadcrumbsData {
134
- selectedNode: DOMNode|null;
135
- crumbs: DOMNode[];
136
- }
137
-
138
- export class ElementsBreadcrumbs extends HTMLElement {
139
- // ...
140
- #crumbs: DOMNode[] = [];
141
- #selectedNode: DOMNode|null = null;
142
-
143
- set data(data: ElementsBreadcrumbsData) {
144
- this.#crumbs = data.crumbs;
145
- this.#selectedNode = data.selectedNode;
146
- void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
147
- }
148
- }
149
- ```
150
-
151
- ## Rendering components
152
-
153
- Now we know how to create components that can take data, let's render them!
154
-
155
- ### From a DevTools Widget
156
-
157
- If you are rendering a component from the DevTools widget system, you should instantiate the component, pass any `data` to it, and then append it into the DOM:
158
-
159
- ```ts
160
- // Within a Widget
161
- const breadcrumbs = new ElementsBreadcrumbs();
162
- breadcrumbs.data = {selectedNode: node, crumbs: [...]};
163
- this.appendChild(breadcrumbs);
164
- ```
165
-
166
- ### From another component
167
-
168
- If you are rendering a component from within another component, import the module defining it and render within the call to `LitHtml.html`:
169
-
170
- ```ts
171
- // Within some component
172
- LitHtml.render(html`
173
- <devtools-elements-breadcrumbs></devtools-elements-breadcrumbs>
174
- `, this.#shadow, {host: this});
175
- ```
176
-
177
- To pass data, we use [LitHtml's dot syntax](https://lit.dev/docs/templates/expressions/#property-expressions) to set the `data` property (and invoke our `set data` setter):
178
-
179
- ```ts
180
- // Within some component
181
- LitHtml.render(html`
182
- <devtools-elements-breadcrumbs .data=${{
183
- selectedNode: node,
184
- crumbs: [...],
185
- }}>
186
- </devtools-elements-breadcrumbs>
187
- `, this.#shadow, {host: this});
188
- ```
189
-
190
- ## Performance concerns with data passing
191
-
192
- The approach of `set data(data)` was chosen because:
193
-
194
- 1. It requires few lines of code.
195
- 2. It provides some form of type safety via the `as FooData` check.
196
- 3. At the time we didn't have a solution for scheduled and batched renders, and didn't want multiple setters to trigger multiple unnecessary renders.
197
-
198
- However, using `set data(data)` does come with some negative performance costs:
199
-
200
- 1. LitHtml will always think the value has changed, because it's an object. If a component renders twice with `.data=${{name: 'Jack'}}`, Lit will think that the value has changed because it's a new object, even though we can see it's holding the same data.
201
- 2. This approach causes these objects to be created (and subsequently garbage collected) on/after each render.
202
-
203
- For most components in DevTools, these trade-offs are acceptable, and we prefer the type-safety of `set data` and take the usually imperceivable performance hit. However, in rare circumstances, this performance hit is noticeable. A good example of this is in Performance Insights, where we have to constantly re-render components as the user scrolls through their performance timeline. We noticed that this caused a large amount of garbage collection from all the objects being created per-render and then immediately disposed.
204
-
205
- For these situations, we can instead move to an approach where we set properties individually. We still define the interface as before, and then define an individual setter for each property:
206
-
207
- ```ts
208
- interface ElementsBreadcrumbsData {
209
- selectedNode: DOMNode|null;
210
- crumbs: DOMNode[];
211
- }
212
-
213
- class ElementsBreadcrumbs extends HTMLElement {
214
- #crumbs: DOMNode[] = [];
215
- #selectedNode: DOMNode|null = null;
216
-
217
- set crumbs(crumbs: ElementsBreadcrumbsData['crumbs']) {
218
- this.#crumbs = crumbs;
219
- void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
220
- }
221
-
222
- set selectedNode(selectedNode: ElementsBreadcrumbsData['selectedNode']) {
223
- this.#selectedNode = selectedNode;
224
- void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
225
- }
226
- }
227
- ```
228
-
229
- Rendering this component within another Lit component would now be done like so:
230
-
231
- ```ts
232
- // Within some component
233
- LitHtml.render(html`
234
- <devtools-elements-breadcrumbs .crumbs=${[...]} .selectedNode=${node}>
235
- </devtools-elements-breadcrumbs>
236
- `, this.#shadow, {host: this});
237
- ```
238
-
239
- This solution is more performant, but less type-safe as TypeScript has no means of checking those values. This is something we may rectify using the `as` pattern, but for now it's preferred to use the `set data` method by default.
240
-
241
-
242
-
@@ -1,23 +0,0 @@
1
- # Building UI elements in DevTools
2
-
3
- This is the documentation containing all the information you need to be able to build UI in DevTools and get familiar with our front-end stack.
4
-
5
- Note that this documentation is a work in progress as it is migrated from the [original Google Doc](https://docs.google.com/document/d/1Gwd-w7LoW1qnWu3uku438-MQ82m4AinKDIdy9z3fl3Q/edit?resourcekey=0-vxHqiKAvfJ4JAOj4bZ4EFA#). crbug.com/1344124 tracks this work.
6
-
7
- ## Table of Contents
8
-
9
- 1. [Creating components](./CreatingComponents.md)
10
- 1. [Styling components](./StylingComponents.md)
11
- 1. [Component performance](./ComponentPerformance.md)
12
- 1. [Testing components](./TestingComponents.md)
13
- 1. [Components and events](./ComponentEvents.md)
14
-
15
- ## FAQs
16
-
17
- > Wasn't this a google doc before?
18
-
19
- Yes. The original version of this documentation was a publicly accessible Google Doc. However, keeping the documentation apart from the code was a challenge, along with the fact that Docs aren't the best for dealing with lots of code. That motivated the change to move the documentation into the repository, hopefully helping it keep up to date and still remaining public.
20
-
21
- > What should I do if I want to make a change?
22
-
23
- Please send a CL and ask jacktfranklin@ to review it. If you're not sure on the change, feel free to ping jacktfranklin@ to discuss.
@@ -1,66 +0,0 @@
1
- # Styling Components
2
-
3
- Components are styled by a CSS file that is co-located next to the TypeScript source file. The only difference in file name is the extension and that for CSS files, the first letter is lowercase:
4
-
5
- ```
6
- - ElementsBreadcrumbs.ts
7
- - elementsBreadcrumbs.css
8
- ```
9
-
10
- To import this file you use a regular `import` statement, and import the filename with `.js` appended:
11
-
12
- ```ts
13
- import elementsBreadcrumbsStyles from './elementsBreadcrumbs.css.js';
14
- ```
15
-
16
- As part of the build tool step, each CSS file is converted into a JS file that exports a `CSSInJS` string.
17
-
18
- ## `BUILD.gn` changes
19
-
20
- Use the `generate_css` action (`scripts/build/ninja/generate_css.gni`) to declare stylesheets:
21
-
22
- ```gn
23
- import("scripts/build/ninja/generate_css.gni") // Edit path correctly: this must be relative
24
-
25
- generate_css("css_files") {
26
- sources = [ "elementsBreadcrumbs.css"]
27
- }
28
- ```
29
-
30
- And then add `:css_files` as a dependency to the `devtools_entrypoint`:
31
-
32
- ```gn
33
- devtools_entrypoint("bundle") {
34
- deps = [ ":css_files" , "..."]
35
- }
36
- ```
37
-
38
- ## Using the stylesheet
39
-
40
- Importing the stylesheet is not enough to have it apply to the component's ShadowDOM, it must be inserted into
41
- a `<style>` tag in the template. The `#render` method is the best place for this:
42
-
43
- ```ts
44
- #render() {
45
- render(html`
46
- <style>${elementsBreadcrumbsStyles}</style>
47
- ...
48
- `, this.#shadow, {host: this});
49
- }
50
- ```
51
-
52
- ## Tips for writing CSS.
53
-
54
- Use `:host` to style the component itself. By default elements are `display: inline`. Often it can be useful to set `display: block`.
55
-
56
- Remember to use theme colors (`var(--sys-color-on-surface)`) when styling elements to ensure consistency across DevTools.
57
-
58
- If you want your component to have its colors configurable by users, consider defining `--override` variables. In your component's CSS, you would have something like:
59
-
60
- ```css
61
- :host {
62
- color: var(--override-custom-color, var(--sys-color-on-surface));
63
- }
64
- ```
65
-
66
- This allows someone to define that `--override-custom-color` variable, but also ensures if it isn't defined that the default `color-text-primary` will be used. **Be careful with this!** We try to ensure consistent colors across DevTools; so most of the time you shouldn't allow configurable colors and should use the correct theme variables.
@@ -1,111 +0,0 @@
1
- # Testing components
2
-
3
- One of the main ways that we test our components is with thorough unit tests. In these tests we render the component into the page, and query the DOM to assert that the component behaves and outputs the HTML as we expect.
4
-
5
- ## Initialising a component in a unit test
6
-
7
- Within a unit test, you can create the component by initialising it and calling any methods on it to set data:
8
-
9
- ```ts
10
- it('outputs the user name onto the screen', async () => {
11
- const component = new DummyComponent();
12
- component.data = {name: 'Jack' /* whatever data the component needs */}
13
- })
14
- ```
15
-
16
- Now we have a component, we need to render it into the page. You can use the `renderElementIntoDOM` helper method, which is defined in `test/unittests/front_end/helpers/DOMHelpers.js`. It is recommended to use this helper because it will automatically remove the component from the page after the test is done. It is important that the DOM is cleaned after each test to ensure that tests don't inadvertently change the behaviour of future tests.
17
-
18
- ```ts
19
- import {
20
- renderElementIntoDOM,
21
- // adjust this path as required depending on the location of your test file
22
- } from '../../helpers/DOMHelpers.js';
23
-
24
- it('outputs the user name onto the screen', async () => {
25
- const component = new DummyComponent();
26
- component.data = {name: 'Jack' /* whatever data the component needs */}
27
- renderElementIntoDOM(component)
28
- })
29
- ```
30
-
31
- ## Waiting for the component to render
32
-
33
- If your component is implemented using the scheduled renderer, you will need to wait for the render to be complete before continuing with the test. Otherwise you may try to query the DOM before the component has finished rendering.
34
-
35
- ```ts
36
- import * as RenderCoordinator from '../../../../../front_end/ui/components/render_coordinator/render_coordinator.js';
37
-
38
- it('outputs the user name onto the screen', async () => {
39
- const component = new DummyComponent();
40
- component.data = {name: 'Jack' /* whatever data the component needs */}
41
- renderElementIntoDOM(component);
42
- await RenderCoordinator.done(); // Ensure the component is rendered onto the page.
43
- })
44
- ```
45
-
46
- ## Querying the component
47
-
48
- Most of our components use the [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) to render their content. Therefore when querying the contents of the component in a test we need to query the contents of its shadow DOM.
49
-
50
- The shadow DOM for a component can be accessed via `component.shadowRoot`. In TypeScript, this is typed as `ShadowRoot | null`. We therefore have another helper in `DOMHelpers.js` that can check that the shadow root is available, and fail the test otherwise. This will also satisfy TypeScript that the shadow root is present, and avoids you having to continually guard against it being `null`:
51
-
52
- ```ts
53
- it('outputs the user name onto the screen', async () => {
54
- const component = new DummyComponent();
55
- component.data = {name: 'Jack' /* whatever data the component needs */}
56
- renderElementIntoDOM(component);
57
- await RenderCoordinator.done();
58
- assert.isNotNull(component.shadowRoot); // Check that the Shadow Root exists.
59
- })
60
- ```
61
-
62
- Now we are ready to query the DOM and make assertions! You can use the regular DOM APIs such as `querySelector` / `querySelectorAll`:
63
-
64
- ```ts
65
- it('outputs the user name onto the screen', async () => {
66
- const component = new DummyComponent();
67
- component.data = {name: 'Jack' /* whatever data the component needs */}
68
- renderElementIntoDOM(component);
69
- await RenderCoordinator.done();
70
- assert.isNotNull(component.shadowRoot);
71
- const name = component.shadowRoot.querySelector<HTMLElement>('span.name');
72
- })
73
- ```
74
-
75
- Note that by default TypeScript will type the result as `Element|null`. Sometimes it is useful to tell TypeScript more specifically what type of element you are expecting, as not all APIs (such as `textContent`) are available on the `Element` type.
76
-
77
- ## Ensuring queries returned elements
78
-
79
- Any DOM querying API could return back `null` if the element was not found. You can use the `assert.instanceOf` method from Chai to ensure that if the element was not found, the test fails. This also satisfies TypeScript and (much like with the shadow root check earlier) avoids you having to continually code against the element not being found.
80
-
81
-
82
- ```ts
83
- it('outputs the user name onto the screen', async () => {
84
- const component = new DummyComponent();
85
- component.data = {name: 'Jack' /* whatever data the component needs */}
86
- renderElementIntoDOM(component);
87
- await RenderCoordinator.done();
88
- assert.isNotNull(component.shadowRoot);
89
- const name = component.shadowRoot.querySelector<HTMLElement>('span.name');
90
- assert.instanceOf(name, HTMLSpanElement);
91
- })
92
- ```
93
-
94
- ## Asserting on the contents
95
-
96
- Now we have an element that exists, we can query for its contents and assert against them:
97
-
98
- ```ts
99
- it('outputs the user name onto the screen', async () => {
100
- const component = new DummyComponent();
101
- component.data = {name: 'Jack' /* whatever data the component needs */}
102
- renderElementIntoDOM(component);
103
- await RenderCoordinator.done();
104
- assert.isNotNull(component.shadowRoot);
105
- const name = component.shadowRoot.querySelector<HTMLElement>('span.name');
106
- assert.instanceOf(name, HTMLSpanElement);
107
- assert.strictEqual(name.innerText, 'Jack');
108
- })
109
- ```
110
-
111
- And with that, our basic unit test is written.
@@ -1,24 +0,0 @@
1
- // Copyright 2020 The Chromium Authors
2
- // Use of this source code is governed by a BSD-style license that can be
3
- // found in the LICENSE file.
4
-
5
- // Ensure all image variables are defined in the component docs.
6
- import '../../../Images/Images.js';
7
-
8
- import * as CreateBreadcrumbs from './create_breadcrumbs.js';
9
-
10
- CreateBreadcrumbs.init();
11
-
12
- // This can be used by tests to hide the UI elements that are part of the component docs interface.
13
- // E.g., this is useful for screenshot tests.
14
- window.addEventListener('hidecomponentdocsui', () => {
15
- for (const node of document.querySelectorAll<HTMLElement>('.component-docs-ui')) {
16
- node.style.display = 'none';
17
- }
18
- });
19
-
20
- window.addEventListener('showcomponentdocsui', () => {
21
- for (const node of document.querySelectorAll<HTMLElement>('.component-docs-ui')) {
22
- node.style.display = '';
23
- }
24
- });
@@ -1,53 +0,0 @@
1
- /*
2
- * Copyright 2021 The Chromium Authors
3
- * Use of this source code is governed by a BSD-style license that can be
4
- * found in the LICENSE file.
5
- */
6
-
7
- @import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap');
8
-
9
- body {
10
- margin: 0;
11
- padding: 0;
12
- }
13
-
14
- #index-page h1 {
15
- text-align: left;
16
- border-bottom: 1px solid var(--sys-color-divider);
17
- padding: 10px;
18
- width: 100%;
19
- margin-bottom: 10px;
20
- }
21
-
22
- .components-list {
23
- display: flex;
24
- flex-wrap: wrap;
25
- list-style: none;
26
- margin: 0;
27
- padding: 10px;
28
- }
29
-
30
- .components-list li {
31
- width: 20%;
32
- min-width: 150px;
33
- max-width: 300px;
34
- margin: 0 12px 12px 0;
35
- }
36
-
37
- .components-list a {
38
- display: block;
39
- font-size: 14px;
40
- padding: 10px;
41
- }
42
-
43
- .components-list a:link,
44
- .components-list a:visited {
45
- text-transform: capitalize;
46
- color: var(--sys-color-primary);
47
- text-decoration: none;
48
- box-shadow: var(--drop-shadow);
49
- }
50
-
51
- .components-list a:hover {
52
- box-shadow: var(--drop-shadow-depth-3);
53
- }