chrome-devtools-frontend 1.0.1026160 → 1.0.1026673

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 (26) hide show
  1. package/docs/triage_guidelines.md +1 -122
  2. package/front_end/core/i18n/locales/en-US.json +18 -0
  3. package/front_end/core/i18n/locales/en-XL.json +18 -0
  4. package/front_end/core/sdk/SourceMap.ts +22 -6
  5. package/front_end/generated/protocol.ts +4 -0
  6. package/front_end/legacy_test_runner/sources_test_runner/DebuggerTestRunner.js +1 -1
  7. package/front_end/models/bindings/CompilerScriptMapping.ts +1 -1
  8. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +7 -1
  9. package/front_end/models/bindings/IgnoreListManager.ts +18 -20
  10. package/front_end/models/issues_manager/descriptions/clientHintMetaTagAllowListInvalidOrigin.md +1 -1
  11. package/front_end/models/issues_manager/descriptions/clientHintMetaTagModifiedHTML.md +1 -1
  12. package/front_end/models/text_utils/TextRange.ts +8 -0
  13. package/front_end/models/timeline_model/TimelineModel.ts +18 -1
  14. package/front_end/panels/elements/ElementsTreeOutline.ts +10 -16
  15. package/front_end/panels/elements/TopLayerContainer.ts +16 -22
  16. package/front_end/panels/lighthouse/LighthouseController.ts +3 -0
  17. package/front_end/panels/security/SecurityPanel.ts +52 -0
  18. package/front_end/panels/security/originView.css +1 -1
  19. package/front_end/panels/sources/CallStackSidebarPane.ts +2 -3
  20. package/front_end/panels/sources/DebuggerPlugin.ts +2 -2
  21. package/front_end/panels/sources/navigatorTree.css +3 -3
  22. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +1 -1
  23. package/front_end/ui/components/docs/building-ui-documentation/CreatingComponents.md +172 -1
  24. package/front_end/ui/components/text_editor/TextEditor.ts +3 -0
  25. package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +5 -4
  26. package/package.json +1 -1
@@ -1,124 +1,3 @@
1
1
  # Triage Guidelines
2
2
 
3
- ## Disclaimer
4
-
5
- The most important thing: please use common sense. The guidelines below are likely not exhaustive and do not cover every case.
6
-
7
- ## What should be triaged?
8
-
9
- All `Untriaged` DevTools issues, as well as any `Unconfirmed` issues that also have the `TE-NeedsTriageHelp` label need to be triaged.
10
-
11
- [[Query]](https://bugs.chromium.org/p/chromium/issues/list?sort=-modified&q=-label%3ADevTools-Triaged%20component%3APlatform%3EDevTools%20status%3AUntriaged%20OR%20component%3APlatform%3EDevTools%20status%3AUnconfirmed%20label%3ATE-NeedsTriageHelp)
12
-
13
- ## Who is triaging?
14
-
15
- Right now there is a Google-internal rotation set-up, with people that do weekly shifts.
16
- As Microsoft is in an opposite timezone they have a similar rotation on the same triage queue, but during a different time.
17
-
18
- - Google rotation: GMT+1
19
- - MS rotation: GMT-9
20
-
21
- ## What are the SLOs?
22
-
23
- [[Query]](https://bugs.chromium.org/p/chromium/issues/list?q=component%3APlatform%3EDevTools%20status%3AUntriaged%20modified-before%3Atoday-7%20OR%20component%3APlatform%3EDevTools%20status%3AUnconfirmed%20label%3ATE-NeedsTriageHelp%20modified-before%3Atoday-7)
24
-
25
- Issues in the untriaged queue should receive a meaningful response within a business week. This means that the goal is to make the query mentioned above return zero issues.
26
-
27
- ## How are priorities defined?
28
-
29
- - P0: This needs to be urgently done, fire drill alert!
30
- - Most of the time, this seldom needs to be used.
31
- - Critical security exploits that affect stable are a good example for a P0
32
- - DevTools crashing on startup is a good example
33
- - P1: This is important. Let’s aim to get this done next
34
- - Non-critical security fixes will likely be in this category
35
- - Regression bugs will be likely in this category
36
- - Important features that partners are waiting for might be in this category
37
- - P2: We want to do that. The exact time of delivery is not that important though.
38
- - General feature work will likely be in this category
39
- - Non-severe bugs
40
- - P3: This is nice, but not important. We unlikely will do work here.
41
- - Edge-case bugs might fit this category
42
- - Non-important feature requests too
43
-
44
- ## How should issues be triaged?
45
-
46
- - Close issues as `WontFix` if they don't reproduce (in simple cases) or are requests for features we've already concluded not to pursue.
47
- - Close issues as `Archived` that are valid, but it seems unlikely that we will get there anytime soon.
48
- - Move issues out of `Platform>DevTools` if they are not DevTools issues (but just reported via the menu item in DevTools), put on the `DevTools-Triaged` label and leave the `Untriaged` status as is.
49
- - Assign regression bugs with bisects to individuals directly and set the status to `Assigned`.
50
- - Put proper `Platform>DevTools>XXX` component(s) on the issue and do an initial check-in regarding the priority.
51
- - Put one of the following labels on it and set the status to `Available`:
52
- - `Hotlist-DevTools-ProductReview` if it's controversial or clear that consensus needs to be built first.
53
- - `Team-DevTools-BrowserAutomation` if it's an issue related to ChromeDriver or puppeteer.
54
- - `Team-DevTools-RuntimeDebugging` if it's a JavaScript or WebAssembly debugging issue.
55
- - `Team-DevTools-WebDebugging` if it's a Web specific debugging issue (i.e. Network or Application panel).
56
- - `Team-DevTools-DesignAccessibility` if it's a design or accessibility issue.
57
- - `Team-DevTools-Performance` if the issue is related to our performance tooling (i.e. Performance panel, Lighthouse).
58
- - `Team-DevTools-RecordReplay` if it's an issue with the recorder or puppeteer.
59
- - No specific `Team` or `Hotlist` if it doesn't fit any specific team otherwise. Make sure to have the `Platform>DevTools` component (or a subcomponent) on it though.
60
- - Also remember to put the `Needs-UX` label on it, if help from a designer is likely to be required.
61
-
62
- ### Setting Assigned or Available
63
-
64
- Set issues to `Available` if they don’t need immediate action and nobody right now and in the short-term future (an iteration) needs to work on it.
65
-
66
- Issues that are handled by Microsoft have the label “Hotlist-DevTools-MS-Backlog” and “Hotlist-DevTools-MS-CurrentSprint” respectively and can be considered triaged.
67
-
68
- If you think they are super urgent, please assign them to yangguo@chromium.org and cc bmeurer@chromium.org and hablich@chromium.org.
69
-
70
- ### Closing issues
71
-
72
- Don’t be afraid to close issues with WontFix if:
73
-
74
- - Bugs that are not reproducible
75
- - After two weeks you did not get a response back from the reporter on a question
76
- - The requested “bug” is the intended behavior
77
- Make sure that you bundle the WontFix with a brief comment explaining it e.g. “Setting to WontFix because not reproducible.”
78
-
79
- ## FAQ
80
-
81
- ### What if the issue belongs to another team?
82
-
83
- If you think the to-triage issue is not a DevTools issue, please simply set it to a component that you think it should belong to and potentially remove the DevTools component. Make sure that the status is set to Untriaged. Please also ensure that you add the label DevTools-Triaged to the bug to ensure that the bug does not come back to the DevTools component. Feel free to CC people that you think might help with triaging this.
84
- This essentially moves the issue out of the DevTools triage queue into another team’s queue.
85
-
86
- ### What if the issue is best handled by Microsoft?
87
-
88
- If you think the to-triage issue or feature request is best handled by Microsoft then add the label "msft-consider" to the issue along with completing the other normal triage steps.
89
-
90
- ### There is a feature request I am unsure how to handle. What should I do?
91
-
92
- Please set the request to Available and add the label “Hotlist-DevTools-ProductReview”.
93
-
94
- ### How do I indicate that a bug should block a release?
95
-
96
- The combination of the label “M-<milestone>” and “Release-Block-<channel>” signals that this very bug is blocking a release. Examples:
97
-
98
- - M-80, Release-Block-Stable
99
- - This blocks the release of 80 to the Stable channel
100
- - Depends in which release channel 80 is, this might not be an urgent (but still important bug to fix)
101
- - M-81, Release-Block-Beta
102
- - This blocks the release of 81 to the Beta channel
103
- - Depends in which release channel 81 is, this might not be an urgent (but still important bug to fix)
104
- - M-81, Release-Block-Dev
105
- - This blocks the release of 81 to the Dev channel
106
- - This typically means that the bug is urgent and important, as Dev releases are happening every week and are ok to be a little bit buggy.
107
-
108
- ## Out of scope
109
-
110
- ### Managing the backlog
111
-
112
- [[Query]](https://bugs.chromium.org/p/chromium/issues/list?q=component%3APlatform%3EDevTools%20status%3AAvailable)
113
-
114
- Managing the backlog is out of scope for the triage rotation. The backlog will be groomed continuously by hablich@ for now. The SLA is that there should be a maximum of 50 issues in there.
115
-
116
- ### Managing the ProductReview queue
117
-
118
- [[Query]](https://bugs.chromium.org/p/chromium/issues/list?q=Hotlist%3DDevTools-ProductReview)
119
-
120
- Issues in `ProductReview` will continuously be handled by hablich@ to unblock items in there. SLA is max 10 issues.
121
-
122
- ## References
123
-
124
- - [Chromium triage guidelines](https://www.chromium.org/for-testers/bug-reporting-guidelines/triage-best-practices)
3
+ Moved to [go/chrome-devtools/triage-sheriff](http://go/chrome-devtools/triage-sheriff).
@@ -1709,6 +1709,12 @@
1709
1709
  "models/timeline_model/TimelineModel.ts | workerSS": {
1710
1710
  "message": "Worker: {PH1} — {PH2}"
1711
1711
  },
1712
+ "models/timeline_model/TimelineModel.ts | workletService": {
1713
+ "message": "Auction Worklet Service"
1714
+ },
1715
+ "models/timeline_model/TimelineModel.ts | workletServiceS": {
1716
+ "message": "Auction Worklet Service — {PH1}"
1717
+ },
1712
1718
  "models/workspace/UISourceCode.ts | index": {
1713
1719
  "message": "(index)"
1714
1720
  },
@@ -8921,6 +8927,12 @@
8921
8927
  "panels/security/SecurityPanel.ts | contentWithCertificateErrors": {
8922
8928
  "message": "content with certificate errors"
8923
8929
  },
8930
+ "panels/security/SecurityPanel.ts | enabled": {
8931
+ "message": "enabled"
8932
+ },
8933
+ "panels/security/SecurityPanel.ts | encryptedClientHello": {
8934
+ "message": "Encrypted ClientHello"
8935
+ },
8924
8936
  "panels/security/SecurityPanel.ts | flaggedByGoogleSafeBrowsing": {
8925
8937
  "message": "Flagged by Google Safe Browsing"
8926
8938
  },
@@ -9044,6 +9056,9 @@
9044
9056
  "panels/security/SecurityPanel.ts | securityOverview": {
9045
9057
  "message": "Security overview"
9046
9058
  },
9059
+ "panels/security/SecurityPanel.ts | serverSignature": {
9060
+ "message": "Server signature"
9061
+ },
9047
9062
  "panels/security/SecurityPanel.ts | showFullDetails": {
9048
9063
  "message": "Show full details"
9049
9064
  },
@@ -9146,6 +9161,9 @@
9146
9161
  "panels/security/SecurityPanel.ts | unknownCanceled": {
9147
9162
  "message": "Unknown / canceled"
9148
9163
  },
9164
+ "panels/security/SecurityPanel.ts | unknownField": {
9165
+ "message": "unknown"
9166
+ },
9149
9167
  "panels/security/SecurityPanel.ts | validAndTrusted": {
9150
9168
  "message": "valid and trusted"
9151
9169
  },
@@ -1709,6 +1709,12 @@
1709
1709
  "models/timeline_model/TimelineModel.ts | workerSS": {
1710
1710
  "message": "Worker: {PH1} — {PH2}"
1711
1711
  },
1712
+ "models/timeline_model/TimelineModel.ts | workletService": {
1713
+ "message": "Âúĉt́îón̂ Ẃôŕk̂ĺêt́ Ŝér̂v́îćê"
1714
+ },
1715
+ "models/timeline_model/TimelineModel.ts | workletServiceS": {
1716
+ "message": "Âúĉt́îón̂ Ẃôŕk̂ĺêt́ Ŝér̂v́îćê — {PH1}"
1717
+ },
1712
1718
  "models/workspace/UISourceCode.ts | index": {
1713
1719
  "message": "(îńd̂éx̂)"
1714
1720
  },
@@ -8921,6 +8927,12 @@
8921
8927
  "panels/security/SecurityPanel.ts | contentWithCertificateErrors": {
8922
8928
  "message": "ĉón̂t́êńt̂ ẃît́ĥ ćêŕt̂íf̂íĉát̂é êŕr̂ór̂ś"
8923
8929
  },
8930
+ "panels/security/SecurityPanel.ts | enabled": {
8931
+ "message": "êńâb́l̂éd̂"
8932
+ },
8933
+ "panels/security/SecurityPanel.ts | encryptedClientHello": {
8934
+ "message": "Êńĉŕŷṕt̂éd̂ Ćl̂íêńt̂H́êĺl̂ó"
8935
+ },
8924
8936
  "panels/security/SecurityPanel.ts | flaggedByGoogleSafeBrowsing": {
8925
8937
  "message": "F̂ĺâǵĝéd̂ b́ŷ Ǵôóĝĺê Śâf́ê B́r̂óŵśîńĝ"
8926
8938
  },
@@ -9044,6 +9056,9 @@
9044
9056
  "panels/security/SecurityPanel.ts | securityOverview": {
9045
9057
  "message": "Ŝéĉúr̂ít̂ý ôv́êŕv̂íêẃ"
9046
9058
  },
9059
+ "panels/security/SecurityPanel.ts | serverSignature": {
9060
+ "message": "Ŝér̂v́êŕ ŝíĝńât́ûŕê"
9061
+ },
9047
9062
  "panels/security/SecurityPanel.ts | showFullDetails": {
9048
9063
  "message": "Ŝh́ôẃ f̂úl̂ĺ d̂ét̂áîĺŝ"
9049
9064
  },
@@ -9146,6 +9161,9 @@
9146
9161
  "panels/security/SecurityPanel.ts | unknownCanceled": {
9147
9162
  "message": "Ûńk̂ńôẃn̂ / ćâńĉél̂éd̂"
9148
9163
  },
9164
+ "panels/security/SecurityPanel.ts | unknownField": {
9165
+ "message": "ûńk̂ńôẃn̂"
9166
+ },
9149
9167
  "panels/security/SecurityPanel.ts | validAndTrusted": {
9150
9168
  "message": "v̂ál̂íd̂ án̂d́ t̂ŕûśt̂éd̂"
9151
9169
  },
@@ -77,6 +77,9 @@ export interface SourceMap {
77
77
  SourceMapEntry|null;
78
78
  mappings(): SourceMapEntry[];
79
79
  mapsOrigin(): boolean;
80
+ hasIgnoreListHint(sourceURL: Platform.DevToolsPath.UrlString): boolean;
81
+ findRanges(predicate: (sourceURL: Platform.DevToolsPath.UrlString) => boolean, options: {isStartMatching: boolean}):
82
+ TextUtils.TextRange.TextRange[];
80
83
  }
81
84
 
82
85
  export class SourceMapV3 {
@@ -595,19 +598,32 @@ export class TextSourceMap implements SourceMap {
595
598
  }
596
599
 
597
600
  /**
598
- * Returns a list of ranges in the generated script, which are known to be
599
- * third-party code that should be ignore-listed. Each range is a [begin, end)
600
- * pair, meaning that code at the beginning location, up to but not including
601
- * the end location, is known to be third-party code.
601
+ * Returns a list of ranges in the generated script for original sources that
602
+ * match a predicate. Each range is a [begin, end) pair, meaning that code at
603
+ * the beginning location, up to but not including the end location, matches
604
+ * the predicate.
602
605
  */
603
- ignoreListRanges(): TextUtils.TextRange.TextRange[] {
606
+ findRanges(predicate: (sourceURL: Platform.DevToolsPath.UrlString) => boolean, options?: {isStartMatching: boolean}):
607
+ TextUtils.TextRange.TextRange[] {
604
608
  const mappings = this.mappings();
605
609
  const ranges = [];
606
610
 
611
+ if (!mappings.length) {
612
+ return [];
613
+ }
614
+
607
615
  let current: TextUtils.TextRange.TextRange|null = null;
608
616
 
617
+ // If the first mapping isn't at the beginning of the original source, it's
618
+ // up to the caller to decide if it should be considered matching the
619
+ // predicate or not. By default, it's not.
620
+ if ((mappings[0].lineNumber !== 0 || mappings[0].columnNumber !== 0) && options?.isStartMatching) {
621
+ current = TextUtils.TextRange.TextRange.createUnboundedFromLocation(0, 0);
622
+ ranges.push(current);
623
+ }
624
+
609
625
  for (const {sourceURL, lineNumber, columnNumber} of mappings) {
610
- const ignoreListHint = sourceURL && this.hasIgnoreListHint(sourceURL);
626
+ const ignoreListHint = sourceURL && predicate(sourceURL);
611
627
 
612
628
  if (!current && ignoreListHint) {
613
629
  current = TextUtils.TextRange.TextRange.createUnboundedFromLocation(lineNumber, columnNumber);
@@ -236,6 +236,10 @@ export namespace Accessibility {
236
236
  * This `Node`'s role, whether explicit or implicit.
237
237
  */
238
238
  role?: AXValue;
239
+ /**
240
+ * This `Node`'s Chrome raw role.
241
+ */
242
+ chromeRole?: AXValue;
239
243
  /**
240
244
  * The accessible name for this `Node`.
241
245
  */
@@ -291,7 +291,7 @@ SourcesTestRunner.captureStackTraceIntoString = async function(callFrames, async
291
291
  const script = location.script();
292
292
  const uiLocation = await self.Bindings.debuggerWorkspaceBinding.rawLocationToUILocation(location);
293
293
  const isFramework =
294
- uiLocation ? self.Bindings.ignoreListManager.isIgnoreListedUISourceCode(uiLocation.uiSourceCode) : false;
294
+ uiLocation ? self.Bindings.ignoreListManager.isUserIgnoreListedURL(uiLocation.uiSourceCode.url()) : false;
295
295
 
296
296
  if (options.dropFrameworkCallFrames && isFramework) {
297
297
  continue;
@@ -247,7 +247,7 @@ export class CompilerScriptMapping implements DebuggerSourceMapping {
247
247
  const sourceMap = event.data.sourceMap;
248
248
  await this.removeStubUISourceCode(script);
249
249
 
250
- if (IgnoreListManager.instance().isIgnoreListedURL(script.sourceURL, script.isContentScript())) {
250
+ if (IgnoreListManager.instance().isUserIgnoreListedURL(script.sourceURL, script.isContentScript())) {
251
251
  this.sourceMapAttachedForTest(sourceMap);
252
252
  return;
253
253
  }
@@ -509,7 +509,13 @@ export class Location extends LiveLocationWithPool {
509
509
 
510
510
  async isIgnoreListed(): Promise<boolean> {
511
511
  const uiLocation = await this.uiLocation();
512
- return uiLocation ? IgnoreListManager.instance().isIgnoreListedUISourceCode(uiLocation.uiSourceCode) : false;
512
+ if (!uiLocation) {
513
+ return false;
514
+ }
515
+ const manager = this.rawLocation.debuggerModel.sourceMapManager();
516
+ const script = this.rawLocation.script();
517
+ const map = script ? await manager.sourceMapForClientPromise(script) : null;
518
+ return IgnoreListManager.instance().isUserOrSourceMapIgnoreListedUISourceCode(uiLocation.uiSourceCode, map);
513
519
  }
514
520
  }
515
521
 
@@ -97,17 +97,25 @@ export class IgnoreListManager implements SDK.TargetManager.SDKModelObserver<SDK
97
97
  return debuggerModel.setBlackboxPatterns(patterns);
98
98
  }
99
99
 
100
- isIgnoreListedUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): boolean {
100
+ isUserOrSourceMapIgnoreListedUISourceCode(
101
+ uiSourceCode: Workspace.UISourceCode.UISourceCode, sourceMap: SDK.SourceMap.SourceMap|null): boolean {
101
102
  const projectType = uiSourceCode.project().type();
102
103
  const isContentScript = projectType === Workspace.Workspace.projectTypes.ContentScripts;
103
104
  if (isContentScript && Common.Settings.Settings.instance().moduleSetting('skipContentScripts').get()) {
104
105
  return true;
105
106
  }
106
107
  const url = this.uiSourceCodeURL(uiSourceCode);
107
- return url ? this.isIgnoreListedURL(url) : false;
108
+ return url ? this.isUserOrSourceMapIgnoreListedURL(url, sourceMap) : false;
108
109
  }
109
110
 
110
- isIgnoreListedURL(url: Platform.DevToolsPath.UrlString, isContentScript?: boolean): boolean {
111
+ isUserOrSourceMapIgnoreListedURL(url: Platform.DevToolsPath.UrlString, sourceMap: SDK.SourceMap.SourceMap|null):
112
+ boolean {
113
+ const userIgnoreListed = this.isUserIgnoreListedURL(url);
114
+ const sourceMapIgnoreListed = sourceMap?.hasIgnoreListHint(url) ?? false;
115
+ return userIgnoreListed || sourceMapIgnoreListed;
116
+ }
117
+
118
+ isUserIgnoreListedURL(url: Platform.DevToolsPath.UrlString, isContentScript?: boolean): boolean {
111
119
  if (this.#isIgnoreListedURLCache.has(url)) {
112
120
  return Boolean(this.#isIgnoreListedURLCache.get(url));
113
121
  }
@@ -137,8 +145,9 @@ export class IgnoreListManager implements SDK.TargetManager.SDKModelObserver<SDK
137
145
 
138
146
  private async updateScriptRanges(script: SDK.Script.Script, sourceMap: SDK.SourceMap.SourceMap|null): Promise<void> {
139
147
  let hasIgnoreListedMappings = false;
140
- if (!IgnoreListManager.instance().isIgnoreListedURL(script.sourceURL, script.isContentScript())) {
141
- hasIgnoreListedMappings = sourceMap ? sourceMap.sourceURLs().some(url => this.isIgnoreListedURL(url)) : false;
148
+ if (!IgnoreListManager.instance().isUserIgnoreListedURL(script.sourceURL, script.isContentScript())) {
149
+ hasIgnoreListedMappings =
150
+ sourceMap?.sourceURLs().some(url => this.isUserOrSourceMapIgnoreListedURL(url, sourceMap)) ?? false;
142
151
  }
143
152
  if (!hasIgnoreListedMappings) {
144
153
  if (scriptToRange.get(script) && await script.setBlackboxedRanges([])) {
@@ -152,21 +161,10 @@ export class IgnoreListManager implements SDK.TargetManager.SDKModelObserver<SDK
152
161
  return;
153
162
  }
154
163
 
155
- const mappings = sourceMap.mappings();
156
- const newRanges: SourceRange[] = [];
157
- if (mappings.length > 0) {
158
- let currentIgnoreListed = false;
159
- if (mappings[0].lineNumber !== 0 || mappings[0].columnNumber !== 0) {
160
- newRanges.push({lineNumber: 0, columnNumber: 0});
161
- currentIgnoreListed = true;
162
- }
163
- for (const mapping of mappings) {
164
- if (mapping.sourceURL && currentIgnoreListed !== this.isIgnoreListedURL(mapping.sourceURL)) {
165
- newRanges.push({lineNumber: mapping.lineNumber, columnNumber: mapping.columnNumber});
166
- currentIgnoreListed = !currentIgnoreListed;
167
- }
168
- }
169
- }
164
+ const newRanges =
165
+ sourceMap
166
+ .findRanges(srcURL => this.isUserOrSourceMapIgnoreListedURL(srcURL, sourceMap), {isStartMatching: true})
167
+ .flatMap(range => [range.start, range.end]);
170
168
 
171
169
  const oldRanges = scriptToRange.get(script) || [];
172
170
  if (!isEqual(oldRanges, newRanges) && await script.setBlackboxedRanges(newRanges)) {
@@ -1,4 +1,4 @@
1
1
  # Client Hint meta tag contained invalid origin
2
2
 
3
- Items in the accept-ch meta tag allow list must be valid origins.
3
+ Items in the delegate-ch meta tag allow list must be valid origins.
4
4
  No special values (e.g. self, none, and *) are permitted.
@@ -1,4 +1,4 @@
1
1
  # Client Hint meta tag modified by javascript
2
2
 
3
- Only accept-ch meta tags in the original HTML sent from the server
3
+ Only delegate-ch meta tags in the original HTML sent from the server
4
4
  are respected. Any injected via javascript (or other means) are ignored.
@@ -230,6 +230,14 @@ export class TextRange {
230
230
  }
231
231
  return this.startLine < lineNumber && lineNumber < this.endLine;
232
232
  }
233
+
234
+ get start(): {lineNumber: number, columnNumber: number} {
235
+ return {lineNumber: this.startLine, columnNumber: this.startColumn};
236
+ }
237
+
238
+ get end(): {lineNumber: number, columnNumber: number} {
239
+ return {lineNumber: this.endLine, columnNumber: this.endColumn};
240
+ }
233
241
  }
234
242
 
235
243
  export class SourceRange {
@@ -94,6 +94,18 @@ const UIStrings = {
94
94
  *@description Title of an auction worklet in the timeline flame chart of the Performance panel
95
95
  */
96
96
  unknownWorklet: 'Auction Worklet',
97
+
98
+ /**
99
+ *@description Title of control thread of a service process for an auction worklet in the timeline flame chart of the Performance panel
100
+ */
101
+ workletService: 'Auction Worklet Service',
102
+
103
+ /**
104
+ *@description Title of control thread of a service process for an auction worklet with known URL in the timeline flame chart of the Performance panel
105
+ * @example {https://google.com} PH1
106
+ */
107
+ workletServiceS: 'Auction Worklet Service — {PH1}',
108
+
97
109
  };
98
110
  const str_ = i18n.i18n.registerUIStrings('models/timeline_model/TimelineModel.ts', UIStrings);
99
111
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -535,7 +547,8 @@ export class TimelineModelImpl {
535
547
  } else {
536
548
  let urlForOther: Platform.DevToolsPath.UrlString|null = null;
537
549
  let workletTypeForOther: WorkletType = WorkletType.NotWorklet;
538
- if (thread.name() === TimelineModelImpl.AuctionWorkletThreadName) {
550
+ if (thread.name() === TimelineModelImpl.AuctionWorkletThreadName ||
551
+ thread.name() === TimelineModelImpl.UtilityMainThreadName) {
539
552
  if (typeof workletUrl !== 'boolean') {
540
553
  urlForOther = workletUrl;
541
554
  }
@@ -822,6 +835,9 @@ export class TimelineModelImpl {
822
835
  } else if (thread.name() === TimelineModelImpl.AuctionWorkletThreadName) {
823
836
  track.url = url || Platform.DevToolsPath.EmptyUrlString;
824
837
  track.name = TimelineModelImpl.nameAuctionWorklet(workletType, url);
838
+ } else if (workletType !== WorkletType.NotWorklet && thread.name() === TimelineModelImpl.UtilityMainThreadName) {
839
+ track.url = url || Platform.DevToolsPath.EmptyUrlString;
840
+ track.name = url ? i18nString(UIStrings.workletServiceS, {PH1: url}) : i18nString(UIStrings.workletService);
825
841
  }
826
842
  this.tracksInternal.push(track);
827
843
 
@@ -1700,6 +1716,7 @@ export namespace TimelineModelImpl {
1700
1716
  export const WorkerThreadNameLegacy = 'DedicatedWorker Thread';
1701
1717
  export const RendererMainThreadName = 'CrRendererMain';
1702
1718
  export const BrowserMainThreadName = 'CrBrowserMain';
1719
+ export const UtilityMainThreadName = 'CrUtilityMain';
1703
1720
  export const AuctionWorkletThreadName = 'AuctionV8HelperThread';
1704
1721
 
1705
1722
  export const DevToolsMetadataEvent = {
@@ -453,6 +453,8 @@ export class ElementsTreeOutline extends
453
453
  }
454
454
  }
455
455
 
456
+ this.createTopLayerContainer();
457
+
456
458
  if (selectedNode) {
457
459
  this.revealAndSelectNode(selectedNode, true);
458
460
  }
@@ -1173,24 +1175,16 @@ export class ElementsTreeOutline extends
1173
1175
  }
1174
1176
 
1175
1177
  return new Promise<void>(resolve => {
1176
- treeElement.node().getChildNodes(() => {
1177
- populatedTreeElements.add(treeElement);
1178
- this.updateModifiedParentNode(treeElement.node());
1179
- resolve();
1180
- });
1181
- })
1182
- .then(() => {
1183
- if (treeElement.node().nodeName() === 'BODY') {
1184
- this.createTopLayerContainer(treeElement);
1185
- }
1186
- });
1178
+ treeElement.node().getChildNodes(() => {
1179
+ populatedTreeElements.add(treeElement);
1180
+ this.updateModifiedParentNode(treeElement.node());
1181
+ resolve();
1182
+ });
1183
+ });
1187
1184
  }
1188
1185
 
1189
- createTopLayerContainer(bodyElement: ElementsTreeElement): void {
1190
- if (!this.topLayerContainer) {
1191
- this.topLayerContainer = new TopLayerContainer(bodyElement);
1192
- }
1193
- this.topLayerContainer.updateBody(bodyElement);
1186
+ createTopLayerContainer(): void {
1187
+ this.topLayerContainer = new TopLayerContainer(this);
1194
1188
  void this.topLayerContainer.throttledUpdateTopLayerElements();
1195
1189
  }
1196
1190
 
@@ -23,29 +23,18 @@ const str_ = i18n.i18n.registerUIStrings('panels/elements/TopLayerContainer.ts',
23
23
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
24
24
 
25
25
  export class TopLayerContainer extends UI.TreeOutline.TreeElement {
26
- treeOutline: ElementsTreeOutline.ElementsTreeOutline|null;
27
- domModel: SDK.DOMModel.DOMModel;
26
+ domContainer: ElementsTreeOutline.ElementsTreeOutline;
28
27
  currentTopLayerElements: Set<ElementsTreeElement>;
29
- bodyElement: ElementsTreeElement;
30
28
  topLayerUpdateThrottler: Common.Throttler.Throttler;
31
29
  #inserted = false;
32
30
 
33
- constructor(bodyElement: ElementsTreeElement) {
31
+ constructor(domContainer: ElementsTreeOutline.ElementsTreeOutline) {
34
32
  super('#top-layer');
35
- this.bodyElement = bodyElement;
36
- this.domModel = bodyElement.node().domModel();
37
- this.treeOutline = null;
33
+ this.domContainer = domContainer;
38
34
  this.currentTopLayerElements = new Set();
39
35
  this.topLayerUpdateThrottler = new Common.Throttler.Throttler(1);
40
36
  }
41
37
 
42
- updateBody(bodyElement: ElementsTreeElement): void {
43
- if (this.bodyElement !== bodyElement) {
44
- this.#inserted = false;
45
- }
46
- this.bodyElement = bodyElement;
47
- }
48
-
49
38
  async throttledUpdateTopLayerElements(): Promise<void> {
50
39
  await this.topLayerUpdateThrottler.schedule(() => this.updateTopLayerElements());
51
40
  }
@@ -55,7 +44,13 @@ export class TopLayerContainer extends UI.TreeOutline.TreeElement {
55
44
  this.removeCurrentTopLayerElementsAdorners();
56
45
  this.currentTopLayerElements = new Set();
57
46
 
58
- const newTopLayerElementsIDs = await this.domModel.getTopLayerElements();
47
+ const domModel = this.domContainer.rootDOMNode?.domModel();
48
+ if (!domModel) {
49
+ this.hidden = true;
50
+ return;
51
+ }
52
+
53
+ const newTopLayerElementsIDs = await domModel.getTopLayerElements();
59
54
  if (!newTopLayerElementsIDs || newTopLayerElementsIDs.length === 0) {
60
55
  this.hidden = true;
61
56
  return;
@@ -63,12 +58,12 @@ export class TopLayerContainer extends UI.TreeOutline.TreeElement {
63
58
 
64
59
  let topLayerElementIndex = 0;
65
60
  for (let i = 0; i < newTopLayerElementsIDs.length; i++) {
66
- const topLayerDOMNode = this.domModel.idToDOMNode.get(newTopLayerElementsIDs[i]);
61
+ const topLayerDOMNode = domModel.idToDOMNode.get(newTopLayerElementsIDs[i]);
67
62
  if (topLayerDOMNode && topLayerDOMNode.nodeName() !== '::backdrop') {
68
63
  const topLayerElementShortcut = new SDK.DOMModel.DOMNodeShortcut(
69
- this.domModel.target(), topLayerDOMNode.backendNodeId(), 0, topLayerDOMNode.nodeName());
64
+ domModel.target(), topLayerDOMNode.backendNodeId(), 0, topLayerDOMNode.nodeName());
70
65
  const topLayerElementRepresentation = new ElementsTreeOutline.ShortcutTreeElement(topLayerElementShortcut);
71
- const topLayerTreeElement = this.bodyElement.treeOutline?.treeElementByNode.get(topLayerDOMNode);
66
+ const topLayerTreeElement = this.domContainer.treeElementByNode.get(topLayerDOMNode);
72
67
  if (!topLayerTreeElement) {
73
68
  continue;
74
69
  }
@@ -78,11 +73,10 @@ export class TopLayerContainer extends UI.TreeOutline.TreeElement {
78
73
  this.currentTopLayerElements.add(topLayerTreeElement);
79
74
  this.appendChild(topLayerElementRepresentation);
80
75
  // Add the element's backdrop if previous top layer element is a backdrop.
81
- const previousTopLayerDOMNode =
82
- (i > 0) ? this.domModel.idToDOMNode.get(newTopLayerElementsIDs[i - 1]) : undefined;
76
+ const previousTopLayerDOMNode = (i > 0) ? domModel.idToDOMNode.get(newTopLayerElementsIDs[i - 1]) : undefined;
83
77
  if (previousTopLayerDOMNode && previousTopLayerDOMNode.nodeName() === '::backdrop') {
84
78
  const backdropElementShortcut = new SDK.DOMModel.DOMNodeShortcut(
85
- this.domModel.target(), previousTopLayerDOMNode.backendNodeId(), 0, previousTopLayerDOMNode.nodeName());
79
+ domModel.target(), previousTopLayerDOMNode.backendNodeId(), 0, previousTopLayerDOMNode.nodeName());
86
80
  const backdropElementRepresentation = new ElementsTreeOutline.ShortcutTreeElement(backdropElementShortcut);
87
81
  topLayerElementRepresentation.appendChild(backdropElementRepresentation);
88
82
  }
@@ -91,7 +85,7 @@ export class TopLayerContainer extends UI.TreeOutline.TreeElement {
91
85
 
92
86
  this.hidden = topLayerElementIndex <= 0;
93
87
  if (!this.hidden && !this.#inserted) {
94
- this.bodyElement.insertChild(this, this.bodyElement.childCount() - 1);
88
+ this.domContainer.appendChild(this);
95
89
  this.#inserted = true;
96
90
  }
97
91
  }
@@ -309,6 +309,9 @@ export class LighthouseController extends Common.ObjectWrapper.ObjectWrapper<Eve
309
309
  return '';
310
310
  }
311
311
  const usageData = await mainTarget.storageAgent().invoke_getUsageAndQuota({origin});
312
+ if (usageData.getError()) {
313
+ return '';
314
+ }
312
315
  const locations = usageData.usageBreakdown.filter(usage => usage.usage)
313
316
  .map(usage => STORAGE_TYPE_NAMES.get(usage.storageType))
314
317
  .map(i18nStringFn => i18nStringFn ? i18nStringFn() : undefined)
@@ -329,6 +329,16 @@ const UIStrings = {
329
329
  */
330
330
  cipher: 'Cipher',
331
331
  /**
332
+ *@description Text in Security Panel that refers to the signature algorithm
333
+ *used by the server for authenticate in the TLS handshake.
334
+ */
335
+ serverSignature: 'Server signature',
336
+ /**
337
+ *@description Text in Security Panel that refers to whether the ClientHello
338
+ *message in the TLS handshake was encrypted.
339
+ */
340
+ encryptedClientHello: 'Encrypted ClientHello',
341
+ /**
332
342
  *@description Sct div text content in Security Panel of the Security panel
333
343
  */
334
344
  certificateTransparency: 'Certificate Transparency',
@@ -442,12 +452,42 @@ const UIStrings = {
442
452
  *@example {2} PH1
443
453
  */
444
454
  showMoreSTotal: 'Show more ({PH1} total)',
455
+ /**
456
+ *@description Shown when a field refers to an option that is unknown to the frontend.
457
+ */
458
+ unknownField: 'unknown',
459
+ /**
460
+ *@description Shown when a field refers to a TLS feature which was enabled.
461
+ */
462
+ enabled: 'enabled',
445
463
  };
446
464
  const str_ = i18n.i18n.registerUIStrings('panels/security/SecurityPanel.ts', UIStrings);
447
465
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
448
466
 
449
467
  let securityPanelInstance: SecurityPanel;
450
468
 
469
+ // See https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-signaturescheme
470
+ // This contains signature schemes supported by Chrome.
471
+ const SignatureSchemeStrings = new Map([
472
+ // The full name for these schemes is RSASSA-PKCS1-v1_5, sometimes
473
+ // "PKCS#1 v1.5", but those are very long, so let "RSA" vs "RSA-PSS"
474
+ // disambiguate.
475
+ [0x0201, 'RSA with SHA-1'],
476
+ [0x0401, 'RSA with SHA-256'],
477
+ [0x0501, 'RSA with SHA-384'],
478
+ [0x0601, 'RSA with SHA-512'],
479
+
480
+ // We omit the curve from these names because in TLS 1.2 these code points
481
+ // were not specific to a curve. Saying "P-256" for a server that used a P-384
482
+ // key with SHA-256 in TLS 1.2 would be confusing.
483
+ [0x0403, 'ECDSA with SHA-256'],
484
+ [0x0503, 'ECDSA with SHA-384'],
485
+
486
+ [0x0804, 'RSA-PSS with SHA-256'],
487
+ [0x0805, 'RSA-PSS with SHA-384'],
488
+ [0x0806, 'RSA-PSS with SHA-512'],
489
+ ]);
490
+
451
491
  export class SecurityPanel extends UI.Panel.PanelWithSidebar implements
452
492
  SDK.TargetManager.SDKModelObserver<SecurityModel> {
453
493
  private readonly mainView: SecurityMainView;
@@ -1466,11 +1506,23 @@ export class SecurityOriginView extends UI.Widget.VBox {
1466
1506
  table.addRow(i18nString(UIStrings.keyExchange), originState.securityDetails.keyExchangeGroup);
1467
1507
  }
1468
1508
 
1509
+ if (originState.securityDetails.serverSignatureAlgorithm) {
1510
+ // See https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-signaturescheme
1511
+ let sigString = SignatureSchemeStrings.get(originState.securityDetails.serverSignatureAlgorithm);
1512
+ sigString ??=
1513
+ i18nString(UIStrings.unknownField) + ' (' + originState.securityDetails.serverSignatureAlgorithm + ')';
1514
+ table.addRow(i18nString(UIStrings.serverSignature), sigString);
1515
+ }
1516
+
1469
1517
  table.addRow(
1470
1518
  i18nString(UIStrings.cipher),
1471
1519
  originState.securityDetails.cipher +
1472
1520
  (originState.securityDetails.mac ? ' with ' + originState.securityDetails.mac : ''));
1473
1521
 
1522
+ if (originState.securityDetails.encryptedClientHello) {
1523
+ table.addRow(i18nString(UIStrings.encryptedClientHello), i18nString(UIStrings.enabled));
1524
+ }
1525
+
1474
1526
  // Create the certificate section outside the callback, so that it appears in the right place.
1475
1527
  const certificateSection = this.element.createChild('div', 'origin-view-section');
1476
1528
  const certificateDiv = certificateSection.createChild('div', 'origin-view-section-title');
@@ -79,7 +79,7 @@
79
79
 
80
80
  .security-origin-view .details-table-row > td:first-child {
81
81
  color: var(--color-text-secondary);
82
- width: calc(110px + 1em);
82
+ width: calc(120px + 1em);
83
83
  text-align: right;
84
84
  padding-right: 1em;
85
85
  }
@@ -219,13 +219,13 @@ export class CallStackSidebarPane extends UI.View.SimpleView implements UI.Conte
219
219
  this.callFrameWarningsElement.classList.add('hidden');
220
220
 
221
221
  const details = UI.Context.Context.instance().flavor(SDK.DebuggerModel.DebuggerPausedDetails);
222
+ this.setSourceMapSubscription(details?.debuggerModel ?? null);
222
223
  if (!details) {
223
224
  this.notPausedMessageElement.classList.remove('hidden');
224
225
  this.ignoreListMessageElement.classList.add('hidden');
225
226
  this.showMoreMessageElement.classList.add('hidden');
226
227
  this.items.replaceAll([]);
227
228
  UI.Context.Context.instance().setFlavor(SDK.DebuggerModel.CallFrame, null);
228
- this.setSourceMapSubscription(null);
229
229
  return;
230
230
  }
231
231
 
@@ -251,7 +251,6 @@ export class CallStackSidebarPane extends UI.View.SimpleView implements UI.Conte
251
251
  }
252
252
 
253
253
  let debuggerModel = details.debuggerModel;
254
- this.setSourceMapSubscription(debuggerModel);
255
254
  let asyncStackTraceId = details.asyncStackTraceId;
256
255
  let asyncStackTrace: Protocol.Runtime.StackTrace|undefined|null = details.asyncStackTrace;
257
256
  let previousStackTrace: Protocol.Runtime.CallFrame[]|SDK.DebuggerModel.CallFrame[] = details.callFrames;
@@ -494,7 +493,7 @@ export class CallStackSidebarPane extends UI.View.SimpleView implements UI.Conte
494
493
  const canIgnoreList =
495
494
  Bindings.IgnoreListManager.IgnoreListManager.instance().canIgnoreListUISourceCode(uiSourceCode);
496
495
  const isIgnoreListed =
497
- Bindings.IgnoreListManager.IgnoreListManager.instance().isIgnoreListedUISourceCode(uiSourceCode);
496
+ Bindings.IgnoreListManager.IgnoreListManager.instance().isUserIgnoreListedURL(uiSourceCode.url());
498
497
  const isContentScript = uiSourceCode.project().type() === Workspace.Workspace.projectTypes.ContentScripts;
499
498
 
500
499
  const manager = Bindings.IgnoreListManager.IgnoreListManager.instance();
@@ -365,7 +365,7 @@ export class DebuggerPlugin extends Plugin {
365
365
  return;
366
366
  }
367
367
  const projectType = uiSourceCode.project().type();
368
- if (!Bindings.IgnoreListManager.IgnoreListManager.instance().isIgnoreListedUISourceCode(uiSourceCode)) {
368
+ if (!Bindings.IgnoreListManager.IgnoreListManager.instance().isUserIgnoreListedURL(uiSourceCode.url())) {
369
369
  this.hideIgnoreListInfobar();
370
370
  return;
371
371
  }
@@ -513,7 +513,7 @@ export class DebuggerPlugin extends Plugin {
513
513
 
514
514
  if (this.uiSourceCode.project().type() === Workspace.Workspace.projectTypes.Network &&
515
515
  Common.Settings.Settings.instance().moduleSetting('jsSourceMapsEnabled').get() &&
516
- !Bindings.IgnoreListManager.IgnoreListManager.instance().isIgnoreListedUISourceCode(this.uiSourceCode)) {
516
+ !Bindings.IgnoreListManager.IgnoreListManager.instance().isUserIgnoreListedURL(this.uiSourceCode.url())) {
517
517
  if (this.scriptFileForDebuggerModel.size) {
518
518
  const scriptFile: Bindings.ResourceScriptMapping.ResourceScriptFile =
519
519
  this.scriptFileForDebuggerModel.values().next().value;
@@ -121,9 +121,9 @@
121
121
  background: var(--override-image-font-tree-item-color);
122
122
  }
123
123
 
124
- .navigator-sm-folder-tree-item .tree-element-title,
125
- .navigator-sm-script-tree-item .tree-element-title,
126
- .navigator-sm-stylesheet-tree-item .tree-element-title {
124
+ .tree-outline:not(:has(.navigator-deployed-tree-item)) .navigator-sm-folder-tree-item .tree-element-title,
125
+ .tree-outline:not(:has(.navigator-deployed-tree-item)) .navigator-sm-script-tree-item .tree-element-title,
126
+ .tree-outline:not(:has(.navigator-deployed-tree-item)) .navigator-sm-stylesheet-tree-item .tree-element-title {
127
127
  font-style: italic;
128
128
  }
129
129
 
@@ -701,7 +701,7 @@ export class TimelineFlameChartDataProvider extends Common.ObjectWrapper.ObjectW
701
701
  }
702
702
 
703
703
  private isIgnoreListedURL(url: Platform.DevToolsPath.UrlString): boolean {
704
- return Bindings.IgnoreListManager.IgnoreListManager.instance().isIgnoreListedURL(url);
704
+ return Bindings.IgnoreListManager.IgnoreListManager.instance().isUserIgnoreListedURL(url);
705
705
  }
706
706
 
707
707
  private appendAsyncEventsGroup(
@@ -78,9 +78,11 @@ Each component defines a `#render` method which is responsible for invoking LitH
78
78
  The `#render` method calls `LitHtml.render`, building up a template with `LitHtml.html`:
79
79
 
80
80
  ```ts
81
- LitHtml.render(LitHtml.html``, this.#shadow, {host: this});
81
+ LitHtml.render(LitHtml.html`<p>hello world!</p>`, this.#shadow, {host: this});
82
82
  ```
83
83
 
84
+ 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.
85
+
84
86
  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:
85
87
 
86
88
  ```ts
@@ -110,3 +112,172 @@ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
110
112
  ```
111
113
 
112
114
  > `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".
115
+
116
+ To summarise, most components start off life looking like:
117
+
118
+ ```ts
119
+ export class ElementsBreadcrumbs extends HTMLElement {
120
+ static readonly litTagName = LitHtml.literal`devtools-chrome-link`;
121
+ readonly #shadow = this.attachShadow({mode: 'open'});
122
+ readonly #boundRender = this.#render.bind(this);
123
+
124
+ #render(): void {
125
+ LitHtml.render(LitHtml.html``, this.#shadow, {host:this});
126
+ }
127
+ }
128
+ ```
129
+
130
+ ## Triggering a render
131
+
132
+ One of the most important aspects to understand about our component system is that **rendering does not happen automatically**.
133
+
134
+ 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):
135
+
136
+ ```ts
137
+ export class ElementsBreadcrumbs extends HTMLElement {
138
+ static readonly litTagName = LitHtml.literal`devtools-chrome-link`;
139
+ readonly #shadow = this.attachShadow({mode: 'open'});
140
+ readonly #boundRender = this.#render.bind(this);
141
+
142
+ connectedCallback(): void {
143
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
144
+ }
145
+
146
+ #render(): void {
147
+ LitHtml.render(LitHtml.html``, this.#shadow, {host:this});
148
+ }
149
+ }
150
+ ```
151
+
152
+ ## Passing data into a component
153
+
154
+ Most of our components will require data that is passed into them. For example, `ElementsBreadcrumbs` takes in an array of `DOMNode` objects.
155
+
156
+ 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:
157
+
158
+ ```ts
159
+ export interface ElementsBreadcrumbsData {
160
+ selectedNode: DOMNode|null;
161
+ crumbs: DOMNode[];
162
+ }
163
+
164
+ export class ElementsBreadcrumbs extends HTMLElement {
165
+ // ...
166
+ #crumbs: DOMNode[] = [];
167
+ #selectedNode: DOMNode|null = null;
168
+
169
+ set data(data: ElementsBreadcrumbsData) {
170
+ this.#crumbs = data.crumbs;
171
+ this.#selectedNode = data.selectedNode;
172
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
173
+ }
174
+ }
175
+ ```
176
+
177
+ ## Rendering components
178
+
179
+ Now we know how to create components that can take data, let's render them!
180
+
181
+ ### From a DevTools Widget
182
+
183
+ 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:
184
+
185
+ ```ts
186
+ // Within a Widget
187
+ const breadcrumbs = new ElementsBreadcrumbs();
188
+ breadcrumbs.data = {selectedNode: node, crumbs: [...]};
189
+ this.appendChild(breadcrumbs);
190
+ ```
191
+
192
+ ### From another component
193
+
194
+ If you are rendering a component from within another component, render it within the call to `LitHtml.html` and use the static `litTagName` property:
195
+
196
+ ```ts
197
+ // Within some component
198
+ LitHtml.render(LitHtml.html`
199
+ <${ElementsBreadcrumbs.litTagName}></${ElementsBreadcrumbs>
200
+ `, this.#shadow, {host: this});
201
+ ```
202
+
203
+ 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):
204
+
205
+ ```ts
206
+ // Within some component
207
+ LitHtml.render(LitHtml.html`
208
+ <${ElementsBreadcrumbs.litTagName} .data=${{
209
+ selectedNode: node,
210
+ crumbs: [...],
211
+ }}>
212
+ </${ElementsBreadcrumbs>
213
+ `, this.#shadow, {host: this});
214
+ ```
215
+
216
+ To enforce some type safety, we also use TypeScript's `as` keyword to force the compiler to type-check the `data` object against the interface:
217
+
218
+ ```ts
219
+ // Within some component
220
+ LitHtml.render(LitHtml.html`
221
+ <${ElementsBreadcrumbs.litTagName} .data=${{
222
+ selectedNode: node,
223
+ crumbs: [...],
224
+ } as ElementsBreadcrumbsData}>
225
+ </${ElementsBreadcrumbs>
226
+ `, this.#shadow, {host: this});
227
+ ```
228
+
229
+ This type-checking requirement is enforced by an ESLint rule.
230
+
231
+ ## Performance concerns with data passing
232
+
233
+ The approach of `set data(data)` was chosen because:
234
+
235
+ 1. It requires few lines of code.
236
+ 2. It provides some form of type safety via the `as FooData` check.
237
+ 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.
238
+
239
+ However, using `set data(data)` does come with some negative performance costs:
240
+
241
+ 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.
242
+ 2. This approach causes these objects to be created (and subsequently garbage collected) on/after each render.
243
+
244
+ 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.
245
+
246
+ 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:
247
+
248
+ ```ts
249
+ interface ElementsBreadcrumbsData {
250
+ selectedNode: DOMNode|null;
251
+ crumbs: DOMNode[];
252
+ }
253
+
254
+ class ElementsBreadcrumbs extends HTMLElement {
255
+ #crumbs: DOMNode[] = [];
256
+ #selectedNode: DOMNode|null = null;
257
+
258
+ set crumbs(crumbs: ElementsBreadcrumbsData['crumbs']) {
259
+ this.#crumbs = crumbs;
260
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
261
+ }
262
+
263
+ set selectedNode(selectedNode: ElementsBreadcrumbsData['selectedNode']) {
264
+ this.#selectedNode = selectedNode;
265
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
266
+ }
267
+ }
268
+ ```
269
+
270
+ Rendering this component within another Lit component would now be done like so:
271
+
272
+ ```ts
273
+ // Within some component
274
+ LitHtml.render(LitHtml.html`
275
+ <${ElementsBreadcrumbs.litTagName} .crumbs=${[...]} .selectedNode=${node}>
276
+ </${ElementsBreadcrumbs>
277
+ `, this.#shadow, {host: this});
278
+ ```
279
+
280
+ 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.
281
+
282
+
283
+
@@ -105,6 +105,9 @@ export class TextEditor extends HTMLElement {
105
105
  connectedCallback(): void {
106
106
  if (!this.#activeEditor) {
107
107
  this.#createEditor();
108
+ } else {
109
+ this.#activeEditor.scrollDOM.scrollTop = this.#lastScrollPos.top;
110
+ this.#activeEditor.scrollDOM.scrollLeft = this.#lastScrollPos.left;
108
111
  }
109
112
  }
110
113
 
@@ -70,7 +70,7 @@ function populateContextMenu(link: Element, event: Event): void {
70
70
  const uiLocation = Linkifier.uiLocation(link);
71
71
  if (uiLocation &&
72
72
  Bindings.IgnoreListManager.IgnoreListManager.instance().canIgnoreListUISourceCode(uiLocation.uiSourceCode)) {
73
- if (Bindings.IgnoreListManager.IgnoreListManager.instance().isIgnoreListedUISourceCode(uiLocation.uiSourceCode)) {
73
+ if (Bindings.IgnoreListManager.IgnoreListManager.instance().isUserIgnoreListedURL(uiLocation.uiSourceCode.url())) {
74
74
  contextMenu.debugSection().appendItem(
75
75
  i18nString(UIStrings.removeFromIgnore),
76
76
  () => Bindings.IgnoreListManager.IgnoreListManager.instance().unIgnoreListUISourceCode(
@@ -127,8 +127,8 @@ export function buildStackTraceRows(
127
127
  // TODO(crbug.com/1183325): fix race condition with uiLocation still being null here
128
128
  const uiLocation = Linkifier.uiLocation(link);
129
129
  if (uiLocation &&
130
- Bindings.IgnoreListManager.IgnoreListManager.instance().isIgnoreListedUISourceCode(
131
- uiLocation.uiSourceCode)) {
130
+ Bindings.IgnoreListManager.IgnoreListManager.instance().isUserIgnoreListedURL(
131
+ uiLocation.uiSourceCode.url())) {
132
132
  ignoreListHide = true;
133
133
  }
134
134
  // Linkifier is using a workaround with the 'zero width space' (\u200b).
@@ -175,7 +175,8 @@ function updateHiddenRows(
175
175
  if ('link' in row && row.link) {
176
176
  const uiLocation = Linkifier.uiLocation(row.link);
177
177
  if (uiLocation &&
178
- Bindings.IgnoreListManager.IgnoreListManager.instance().isIgnoreListedUISourceCode(uiLocation.uiSourceCode)) {
178
+ Bindings.IgnoreListManager.IgnoreListManager.instance().isUserIgnoreListedURL(
179
+ uiLocation.uiSourceCode.url())) {
179
180
  row.ignoreListHide = true;
180
181
  }
181
182
  if (row.rowCountHide || row.ignoreListHide) {
package/package.json CHANGED
@@ -55,5 +55,5 @@
55
55
  "unittest": "scripts/test/run_unittests.py --no-text-coverage",
56
56
  "watch": "vpython third_party/node/node.py --output scripts/watch_build.js"
57
57
  },
58
- "version": "1.0.1026160"
58
+ "version": "1.0.1026673"
59
59
  }