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.
- package/docs/triage_guidelines.md +1 -122
- package/front_end/core/i18n/locales/en-US.json +18 -0
- package/front_end/core/i18n/locales/en-XL.json +18 -0
- package/front_end/core/sdk/SourceMap.ts +22 -6
- package/front_end/generated/protocol.ts +4 -0
- package/front_end/legacy_test_runner/sources_test_runner/DebuggerTestRunner.js +1 -1
- package/front_end/models/bindings/CompilerScriptMapping.ts +1 -1
- package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +7 -1
- package/front_end/models/bindings/IgnoreListManager.ts +18 -20
- package/front_end/models/issues_manager/descriptions/clientHintMetaTagAllowListInvalidOrigin.md +1 -1
- package/front_end/models/issues_manager/descriptions/clientHintMetaTagModifiedHTML.md +1 -1
- package/front_end/models/text_utils/TextRange.ts +8 -0
- package/front_end/models/timeline_model/TimelineModel.ts +18 -1
- package/front_end/panels/elements/ElementsTreeOutline.ts +10 -16
- package/front_end/panels/elements/TopLayerContainer.ts +16 -22
- package/front_end/panels/lighthouse/LighthouseController.ts +3 -0
- package/front_end/panels/security/SecurityPanel.ts +52 -0
- package/front_end/panels/security/originView.css +1 -1
- package/front_end/panels/sources/CallStackSidebarPane.ts +2 -3
- package/front_end/panels/sources/DebuggerPlugin.ts +2 -2
- package/front_end/panels/sources/navigatorTree.css +3 -3
- package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +1 -1
- package/front_end/ui/components/docs/building-ui-documentation/CreatingComponents.md +172 -1
- package/front_end/ui/components/text_editor/TextEditor.ts +3 -0
- package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +5 -4
- package/package.json +1 -1
@@ -1,124 +1,3 @@
|
|
1
1
|
# Triage Guidelines
|
2
2
|
|
3
|
-
|
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
|
599
|
-
*
|
600
|
-
*
|
601
|
-
* the
|
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
|
-
|
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 &&
|
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.
|
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().
|
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
|
-
|
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
|
-
|
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.
|
108
|
+
return url ? this.isUserOrSourceMapIgnoreListedURL(url, sourceMap) : false;
|
108
109
|
}
|
109
110
|
|
110
|
-
|
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().
|
141
|
-
hasIgnoreListedMappings =
|
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
|
156
|
-
|
157
|
-
|
158
|
-
|
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)) {
|
@@ -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
|
-
|
1177
|
-
|
1178
|
-
|
1179
|
-
|
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(
|
1190
|
-
|
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
|
-
|
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(
|
31
|
+
constructor(domContainer: ElementsTreeOutline.ElementsTreeOutline) {
|
34
32
|
super('#top-layer');
|
35
|
-
this.
|
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
|
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 =
|
61
|
+
const topLayerDOMNode = domModel.idToDOMNode.get(newTopLayerElementsIDs[i]);
|
67
62
|
if (topLayerDOMNode && topLayerDOMNode.nodeName() !== '::backdrop') {
|
68
63
|
const topLayerElementShortcut = new SDK.DOMModel.DOMNodeShortcut(
|
69
|
-
|
64
|
+
domModel.target(), topLayerDOMNode.backendNodeId(), 0, topLayerDOMNode.nodeName());
|
70
65
|
const topLayerElementRepresentation = new ElementsTreeOutline.ShortcutTreeElement(topLayerElementShortcut);
|
71
|
-
const topLayerTreeElement = this.
|
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
|
-
|
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.
|
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');
|
@@ -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().
|
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().
|
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().
|
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().
|
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
|
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().
|
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().
|
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().
|
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