chrome-devtools-frontend 1.0.1006211 → 1.0.1007307

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 (125) hide show
  1. package/config/gni/devtools_grd_files.gni +2 -0
  2. package/config/gni/devtools_image_files.gni +2 -0
  3. package/front_end/Images/src/ic_sources_authored.svg +5 -0
  4. package/front_end/Images/src/ic_sources_deployed.svg +5 -0
  5. package/front_end/core/i18n/locales/af.json +202 -43
  6. package/front_end/core/i18n/locales/am.json +201 -42
  7. package/front_end/core/i18n/locales/ar.json +201 -42
  8. package/front_end/core/i18n/locales/as.json +201 -42
  9. package/front_end/core/i18n/locales/az.json +201 -42
  10. package/front_end/core/i18n/locales/be.json +201 -42
  11. package/front_end/core/i18n/locales/bg.json +201 -42
  12. package/front_end/core/i18n/locales/bn.json +201 -42
  13. package/front_end/core/i18n/locales/bs.json +201 -42
  14. package/front_end/core/i18n/locales/ca.json +204 -45
  15. package/front_end/core/i18n/locales/cs.json +201 -42
  16. package/front_end/core/i18n/locales/cy.json +201 -42
  17. package/front_end/core/i18n/locales/da.json +201 -42
  18. package/front_end/core/i18n/locales/de.json +201 -42
  19. package/front_end/core/i18n/locales/el.json +201 -42
  20. package/front_end/core/i18n/locales/en-GB.json +199 -40
  21. package/front_end/core/i18n/locales/en-US.json +43 -1
  22. package/front_end/core/i18n/locales/en-XL.json +43 -1
  23. package/front_end/core/i18n/locales/es-419.json +201 -42
  24. package/front_end/core/i18n/locales/es.json +201 -42
  25. package/front_end/core/i18n/locales/et.json +201 -42
  26. package/front_end/core/i18n/locales/eu.json +202 -43
  27. package/front_end/core/i18n/locales/fa.json +201 -42
  28. package/front_end/core/i18n/locales/fi.json +201 -42
  29. package/front_end/core/i18n/locales/fil.json +201 -42
  30. package/front_end/core/i18n/locales/fr-CA.json +201 -42
  31. package/front_end/core/i18n/locales/fr.json +203 -44
  32. package/front_end/core/i18n/locales/gl.json +203 -44
  33. package/front_end/core/i18n/locales/gu.json +201 -42
  34. package/front_end/core/i18n/locales/he.json +201 -42
  35. package/front_end/core/i18n/locales/hi.json +201 -42
  36. package/front_end/core/i18n/locales/hr.json +201 -42
  37. package/front_end/core/i18n/locales/hu.json +201 -42
  38. package/front_end/core/i18n/locales/hy.json +201 -42
  39. package/front_end/core/i18n/locales/id.json +201 -42
  40. package/front_end/core/i18n/locales/is.json +201 -42
  41. package/front_end/core/i18n/locales/it.json +203 -44
  42. package/front_end/core/i18n/locales/ja.json +203 -44
  43. package/front_end/core/i18n/locales/ka.json +201 -42
  44. package/front_end/core/i18n/locales/kk.json +201 -42
  45. package/front_end/core/i18n/locales/km.json +201 -42
  46. package/front_end/core/i18n/locales/kn.json +201 -42
  47. package/front_end/core/i18n/locales/ko.json +201 -42
  48. package/front_end/core/i18n/locales/ky.json +201 -42
  49. package/front_end/core/i18n/locales/lo.json +201 -42
  50. package/front_end/core/i18n/locales/lt.json +201 -42
  51. package/front_end/core/i18n/locales/lv.json +201 -42
  52. package/front_end/core/i18n/locales/mk.json +201 -42
  53. package/front_end/core/i18n/locales/ml.json +201 -42
  54. package/front_end/core/i18n/locales/mn.json +201 -42
  55. package/front_end/core/i18n/locales/mr.json +201 -42
  56. package/front_end/core/i18n/locales/ms.json +201 -42
  57. package/front_end/core/i18n/locales/my.json +201 -42
  58. package/front_end/core/i18n/locales/ne.json +201 -42
  59. package/front_end/core/i18n/locales/nl.json +201 -42
  60. package/front_end/core/i18n/locales/no.json +201 -42
  61. package/front_end/core/i18n/locales/or.json +201 -42
  62. package/front_end/core/i18n/locales/pa.json +201 -42
  63. package/front_end/core/i18n/locales/pl.json +201 -42
  64. package/front_end/core/i18n/locales/pt-PT.json +201 -42
  65. package/front_end/core/i18n/locales/pt.json +201 -42
  66. package/front_end/core/i18n/locales/ro.json +201 -42
  67. package/front_end/core/i18n/locales/ru.json +203 -44
  68. package/front_end/core/i18n/locales/si.json +201 -42
  69. package/front_end/core/i18n/locales/sk.json +201 -42
  70. package/front_end/core/i18n/locales/sl.json +201 -42
  71. package/front_end/core/i18n/locales/sq.json +201 -42
  72. package/front_end/core/i18n/locales/sr-Latn.json +201 -42
  73. package/front_end/core/i18n/locales/sr.json +201 -42
  74. package/front_end/core/i18n/locales/sv.json +201 -42
  75. package/front_end/core/i18n/locales/sw.json +201 -42
  76. package/front_end/core/i18n/locales/ta.json +201 -42
  77. package/front_end/core/i18n/locales/te.json +202 -43
  78. package/front_end/core/i18n/locales/th.json +201 -42
  79. package/front_end/core/i18n/locales/tr.json +201 -42
  80. package/front_end/core/i18n/locales/uk.json +201 -42
  81. package/front_end/core/i18n/locales/ur.json +201 -42
  82. package/front_end/core/i18n/locales/uz.json +201 -42
  83. package/front_end/core/i18n/locales/vi.json +201 -42
  84. package/front_end/core/i18n/locales/zh-HK.json +201 -42
  85. package/front_end/core/i18n/locales/zh-TW.json +201 -42
  86. package/front_end/core/i18n/locales/zh.json +201 -42
  87. package/front_end/core/i18n/locales/zu.json +201 -42
  88. package/front_end/core/sdk/CPUThrottlingManager.ts +54 -0
  89. package/front_end/core/sdk/DebuggerModel.ts +12 -3
  90. package/front_end/core/sdk/EmulationModel.ts +7 -0
  91. package/front_end/core/sdk/NetworkManager.ts +6 -2
  92. package/front_end/devtools_compatibility.js +1 -0
  93. package/front_end/entrypoints/formatter_worker/FormatterActions.ts +1 -0
  94. package/front_end/entrypoints/formatter_worker/ScopeParser.ts +12 -10
  95. package/front_end/entrypoints/formatter_worker/formatter_worker-entrypoint.ts +4 -0
  96. package/front_end/generated/InspectorBackendCommands.js +3 -0
  97. package/front_end/generated/protocol-mapping.d.ts +2 -0
  98. package/front_end/generated/protocol-proxy-api.d.ts +3 -0
  99. package/front_end/generated/protocol.ts +7 -0
  100. package/front_end/models/formatter/FormatterWorkerPool.ts +6 -0
  101. package/front_end/models/issues_manager/DeprecationIssue.ts +1 -1
  102. package/front_end/models/javascript_metadata/JavaScriptMetadata.ts +13 -20
  103. package/front_end/models/javascript_metadata/NativeFunctions.js +1237 -3962
  104. package/front_end/models/source_map_scopes/NamesResolver.ts +206 -73
  105. package/front_end/models/workspace/UISourceCode.ts +7 -0
  106. package/front_end/panels/accessibility/axBreadcrumbs.css +2 -2
  107. package/front_end/panels/application/components/BackForwardCacheView.ts +16 -0
  108. package/front_end/panels/lighthouse/LighthouseStartView.ts +7 -5
  109. package/front_end/panels/lighthouse/LighthouseStartViewFR.ts +70 -49
  110. package/front_end/panels/mobile_throttling/ThrottlingManager.ts +113 -4
  111. package/front_end/panels/network/components/RequestHeadersView.css +31 -3
  112. package/front_end/panels/network/components/RequestHeadersView.ts +126 -3
  113. package/front_end/panels/sources/NavigatorView.ts +141 -40
  114. package/front_end/panels/sources/SourcesPanel.ts +8 -0
  115. package/front_end/panels/sources/TabbedEditorContainer.ts +2 -2
  116. package/front_end/panels/sources/sources-meta.ts +6 -0
  117. package/front_end/panels/timeline/TimelinePanel.ts +27 -4
  118. package/front_end/panels/timeline/timelinePanel.css +8 -0
  119. package/front_end/ui/components/text_editor/javascript.ts +12 -14
  120. package/front_end/ui/legacy/Treeoutline.ts +5 -2
  121. package/package.json +1 -1
  122. package/scripts/hosted_mode/server.js +14 -1
  123. package/scripts/javascript_natives/helpers.js +26 -7
  124. package/scripts/javascript_natives/index.js +4 -3
  125. package/scripts/javascript_natives/tests.js +2 -2
@@ -2,7 +2,6 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
- import * as Platform from '../../core/platform/platform.js';
6
5
  import * as SDK from '../../core/sdk/sdk.js';
7
6
  import * as Bindings from '../bindings/bindings.js';
8
7
  import * as Formatter from '../formatter/formatter.js';
@@ -12,7 +11,7 @@ import * as Protocol from '../../generated/protocol.js';
12
11
 
13
12
  interface CachedScopeMap {
14
13
  sourceMap: SDK.SourceMap.SourceMap|null;
15
- identifiersPromise: Promise<Map<string, string>>;
14
+ mappingPromise: Promise<{variableMapping: Map<string, string>, thisMapping: string|null}>;
16
15
  }
17
16
 
18
17
  const scopeToCachedIdentifiersMap = new WeakMap<SDK.DebuggerModel.ScopeChainEntry, CachedScopeMap>();
@@ -29,61 +28,138 @@ export class Identifier {
29
28
  }
30
29
  }
31
30
 
32
- export const scopeIdentifiers = async function(scope: SDK.DebuggerModel.ScopeChainEntry): Promise<Identifier[]> {
33
- if (scope.type() === Protocol.Debugger.ScopeType.Global) {
34
- return [];
35
- }
36
- const startLocation = scope.startLocation();
37
- const endLocation = scope.endLocation();
38
- if (!startLocation || !endLocation) {
39
- return [];
31
+ const computeScopeTree = async function(functionScope: SDK.DebuggerModel.ScopeChainEntry): Promise<{
32
+ scopeTree: Formatter.FormatterWorkerPool.ScopeTreeNode, text: TextUtils.Text.Text, slide: number,
33
+ }|null> {
34
+ const functionStartLocation = functionScope.startLocation();
35
+ const functionEndLocation = functionScope.endLocation();
36
+ if (!functionStartLocation || !functionEndLocation) {
37
+ return null;
40
38
  }
41
- const script = startLocation.script();
42
- if (!script || !script.sourceMapURL || script !== endLocation.script()) {
43
- return [];
39
+ const script = functionStartLocation.script();
40
+ if (!script || !script.sourceMapURL || script !== functionEndLocation.script()) {
41
+ return null;
44
42
  }
45
43
  const {content} = await script.requestContent();
46
44
  if (!content) {
47
- return [];
45
+ return null;
48
46
  }
49
47
 
50
48
  const text = new TextUtils.Text.Text(content);
51
49
  const scopeRange = new TextUtils.TextRange.TextRange(
52
- startLocation.lineNumber, startLocation.columnNumber, endLocation.lineNumber, endLocation.columnNumber);
50
+ functionStartLocation.lineNumber, functionStartLocation.columnNumber, functionEndLocation.lineNumber,
51
+ functionEndLocation.columnNumber);
53
52
  const scopeText = text.extract(scopeRange);
54
53
  const scopeStart = text.toSourceRange(scopeRange).offset;
55
54
  const prefix = 'function fui';
56
- const identifiers =
57
- await Formatter.FormatterWorkerPool.formatterWorkerPool().javaScriptIdentifiers(prefix + scopeText);
58
- const result = [];
59
- const cursor = new TextUtils.TextCursor.TextCursor(text.lineEndings());
60
- for (const id of identifiers) {
61
- if (id.offset < prefix.length) {
62
- continue;
63
- }
64
- const start = scopeStart + id.offset - prefix.length;
65
- cursor.resetTo(start);
66
- result.push(new Identifier(id.name, cursor.lineNumber(), cursor.columnNumber()));
55
+ const scopeTree = await Formatter.FormatterWorkerPool.formatterWorkerPool().javaScriptScopeTree(prefix + scopeText);
56
+ if (!scopeTree) {
57
+ return null;
67
58
  }
68
- return result;
59
+ return {scopeTree, text, slide: scopeStart - prefix.length};
69
60
  };
70
61
 
71
- export const resolveScopeChain =
72
- async function(callFrame: SDK.DebuggerModel.CallFrame|null): Promise<SDK.DebuggerModel.ScopeChainEntry[]|null> {
73
- if (!callFrame) {
62
+ export const scopeIdentifiers = async function(
63
+ functionScope: SDK.DebuggerModel.ScopeChainEntry|null, scope: SDK.DebuggerModel.ScopeChainEntry): Promise<{
64
+ freeVariables: Identifier[], boundVariables: Identifier[],
65
+ }|null> {
66
+ if (!functionScope) {
74
67
  return null;
75
68
  }
76
- const {pluginManager} = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
77
- if (pluginManager) {
78
- const scopeChain = await pluginManager.resolveScopeChain(callFrame);
79
- if (scopeChain) {
80
- return scopeChain;
69
+
70
+ const startLocation = scope.startLocation();
71
+ const endLocation = scope.endLocation();
72
+ if (!startLocation || !endLocation) {
73
+ return null;
74
+ }
75
+
76
+ // Parse the function scope to get the scope tree.
77
+ const scopeTreeAndStart = await computeScopeTree(functionScope);
78
+ if (!scopeTreeAndStart) {
79
+ return null;
80
+ }
81
+ const {scopeTree, text, slide} = scopeTreeAndStart;
82
+
83
+ // Compute the offset within the scope tree coordinate space.
84
+ const scopeOffsets = {
85
+ start: text.offsetFromPosition(startLocation.lineNumber, startLocation.columnNumber) - slide,
86
+ end: text.offsetFromPosition(endLocation.lineNumber, endLocation.columnNumber) - slide,
87
+ };
88
+
89
+ if (!contains(scopeTree, scopeOffsets)) {
90
+ return null;
91
+ }
92
+
93
+ // Find the corresponding scope in the scope tree.
94
+ let containingScope = scopeTree;
95
+ const ancestorScopes = [];
96
+ while (true) {
97
+ let childFound = false;
98
+ for (const child of containingScope.children) {
99
+ if (contains(child, scopeOffsets)) {
100
+ // We found a nested containing scope, continue with search there.
101
+ ancestorScopes.push(containingScope);
102
+ containingScope = child;
103
+ childFound = true;
104
+ break;
105
+ }
106
+ // Sanity check: |scope| should not straddle any of the scopes in the tree. That is:
107
+ // Either |scope| is disjoint from |child| or |child| must be inside |scope|.
108
+ // (Or the |scope| is inside |child|, but that case is covered above.)
109
+ if (!disjoint(scopeOffsets, child) && !contains(scopeOffsets, child)) {
110
+ console.error('Wrong nesting of scopes');
111
+ return null;
112
+ }
113
+ }
114
+ if (!childFound) {
115
+ // We found the deepest scope in the tree that contains our scope chain entry.
116
+ break;
81
117
  }
82
118
  }
83
- return callFrame.scopeChain();
119
+
120
+ // Now we have containing scope. Collect all the scope variables.
121
+ const boundVariables = [];
122
+ const cursor = new TextUtils.TextCursor.TextCursor(text.lineEndings());
123
+ for (const variable of containingScope.variables) {
124
+ // Skip the fixed-kind variable (i.e., 'this' or 'arguments') if we only found their "definition"
125
+ // without any uses.
126
+ if (variable.kind === Formatter.FormatterWorkerPool.DefinitionKind.Fixed && variable.offsets.length <= 1) {
127
+ continue;
128
+ }
129
+
130
+ for (const offset of variable.offsets) {
131
+ const start = offset + slide;
132
+ cursor.resetTo(start);
133
+ boundVariables.push(new Identifier(variable.name, cursor.lineNumber(), cursor.columnNumber()));
134
+ }
135
+ }
136
+
137
+ // Compute free variables by collecting all the ancestor variables that are used in |containingScope|.
138
+ const freeVariables = [];
139
+ for (const ancestor of ancestorScopes) {
140
+ for (const ancestorVariable of ancestor.variables) {
141
+ for (const offset of ancestorVariable.offsets) {
142
+ if (offset >= containingScope.start && offset < containingScope.end) {
143
+ const start = offset + slide;
144
+ cursor.resetTo(start);
145
+ freeVariables.push(new Identifier(ancestorVariable.name, cursor.lineNumber(), cursor.columnNumber()));
146
+ }
147
+ }
148
+ }
149
+ }
150
+ return {boundVariables, freeVariables};
151
+
152
+ function contains(scope: {start: number, end: number}, candidate: {start: number, end: number}): boolean {
153
+ return (scope.start <= candidate.start) && (scope.end >= candidate.end);
154
+ }
155
+ function disjoint(scope: {start: number, end: number}, other: {start: number, end: number}): boolean {
156
+ return (scope.end <= other.start) || (other.end <= scope.start);
157
+ }
84
158
  };
85
159
 
86
- export const resolveScope = async(scope: SDK.DebuggerModel.ScopeChainEntry): Promise<Map<string, string>> => {
160
+ const resolveScope =
161
+ async(scope: SDK.DebuggerModel
162
+ .ScopeChainEntry): Promise<{variableMapping: Map<string, string>, thisMapping: string | null}> => {
87
163
  let cachedScopeMap = scopeToCachedIdentifiersMap.get(scope);
88
164
  const script = scope.callFrame().script;
89
165
  const sourceMap = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance().sourceMapForScript(script);
@@ -93,33 +169,59 @@ export const resolveScope = async(scope: SDK.DebuggerModel.ScopeChainEntry): Pro
93
169
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
170
  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
95
171
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
96
- const identifiersPromise = (async(): Promise<Map<any, any>> => {
97
- const namesMapping = new Map<string, string>();
98
- if (sourceMap) {
99
- const textCache = new Map<string, TextUtils.Text.Text>();
100
- // Extract as much as possible from SourceMap and resolve
101
- // missing identifier names from SourceMap ranges.
102
- const promises = [];
103
- for (const id of await scopeIdentifiers(scope)) {
104
- const entry = sourceMap.findEntry(id.lineNumber, id.columnNumber);
105
- if (entry && entry.name) {
106
- namesMapping.set(id.name, entry.name);
107
- } else {
108
- promises.push(resolveSourceName(script, sourceMap, id, textCache).then(sourceName => {
109
- if (sourceName) {
110
- namesMapping.set(id.name, sourceName);
172
+ const identifiersPromise =
173
+ (async(): Promise<{variableMapping: Map<string, string>, thisMapping: string | null}> => {
174
+ const variableMapping = new Map<string, string>();
175
+ let thisMapping = null;
176
+
177
+ if (!sourceMap) {
178
+ return {variableMapping, thisMapping};
179
+ }
180
+ const textCache = new Map<string, TextUtils.Text.Text>();
181
+ // Extract as much as possible from SourceMap and resolve
182
+ // missing identifier names from SourceMap ranges.
183
+ const promises: Promise<void>[] = [];
184
+
185
+ const resolveEntry = (id: Identifier, handler: (sourceName: string) => void): void => {
186
+ const entry = sourceMap.findEntry(id.lineNumber, id.columnNumber);
187
+ if (entry && entry.name) {
188
+ handler(entry.name);
189
+ } else {
190
+ promises.push(resolveSourceName(script, sourceMap, id, textCache).then(sourceName => {
191
+ if (sourceName) {
192
+ handler(sourceName);
193
+ }
194
+ }));
195
+ }
196
+ };
197
+
198
+ const functionScope = findFunctionScope();
199
+ const parsedVariables = await scopeIdentifiers(functionScope, scope);
200
+ if (!parsedVariables) {
201
+ return {variableMapping, thisMapping};
202
+ }
203
+ for (const id of parsedVariables.boundVariables) {
204
+ resolveEntry(id, sourceName => {
205
+ // Let use ignore 'this' mappings - those are handled separately.
206
+ if (sourceName !== 'this') {
207
+ variableMapping.set(id.name, sourceName);
111
208
  }
112
- }));
209
+ });
113
210
  }
114
- }
115
- await Promise.all(promises).then(getScopeResolvedForTest());
116
- }
117
- return namesMapping;
118
- })();
119
- cachedScopeMap = {sourceMap, identifiersPromise};
120
- scopeToCachedIdentifiersMap.set(scope, {sourceMap, identifiersPromise});
211
+ for (const id of parsedVariables.freeVariables) {
212
+ resolveEntry(id, sourceName => {
213
+ if (sourceName === 'this') {
214
+ thisMapping = id.name;
215
+ }
216
+ });
217
+ }
218
+ await Promise.all(promises).then(getScopeResolvedForTest());
219
+ return {variableMapping, thisMapping};
220
+ })();
221
+ cachedScopeMap = {sourceMap, mappingPromise: identifiersPromise};
222
+ scopeToCachedIdentifiersMap.set(scope, {sourceMap, mappingPromise: identifiersPromise});
121
223
  }
122
- return await cachedScopeMap.identifiersPromise;
224
+ return await cachedScopeMap.mappingPromise;
123
225
 
124
226
  async function resolveSourceName(
125
227
  script: SDK.Script.Script, sourceMap: SDK.SourceMap.SourceMap, id: Identifier,
@@ -152,6 +254,39 @@ export const resolveScope = async(scope: SDK.DebuggerModel.ScopeChainEntry): Pro
152
254
  const originalIdentifier = text.extract(sourceTextRange).trim();
153
255
  return /[a-zA-Z0-9_$]+/.test(originalIdentifier) ? originalIdentifier : null;
154
256
  }
257
+
258
+ function findFunctionScope(): SDK.DebuggerModel.ScopeChainEntry|null {
259
+ // First find the scope in the callframe's scope chain and then find the containing function scope (closure or local).
260
+ const scopeChain = scope.callFrame().scopeChain();
261
+ let scopeIndex = 0;
262
+ for (scopeIndex; scopeIndex < scopeChain.length; scopeIndex++) {
263
+ if (scopeChain[scopeIndex] === scope) {
264
+ break;
265
+ }
266
+ }
267
+ for (scopeIndex; scopeIndex < scopeChain.length; scopeIndex++) {
268
+ const kind = scopeChain[scopeIndex].type();
269
+ if (kind === Protocol.Debugger.ScopeType.Local || kind === Protocol.Debugger.ScopeType.Closure) {
270
+ break;
271
+ }
272
+ }
273
+ return scopeIndex === scopeChain.length ? null : scopeChain[scopeIndex];
274
+ }
275
+ };
276
+
277
+ export const resolveScopeChain =
278
+ async function(callFrame: SDK.DebuggerModel.CallFrame|null): Promise<SDK.DebuggerModel.ScopeChainEntry[]|null> {
279
+ if (!callFrame) {
280
+ return null;
281
+ }
282
+ const {pluginManager} = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
283
+ if (pluginManager) {
284
+ const scopeChain = await pluginManager.resolveScopeChain(callFrame);
285
+ if (scopeChain) {
286
+ return scopeChain;
287
+ }
288
+ }
289
+ return callFrame.scopeChain();
155
290
  };
156
291
 
157
292
  export const allVariablesInCallFrame = async(callFrame: SDK.DebuggerModel.CallFrame): Promise<Map<string, string>> => {
@@ -163,8 +298,8 @@ export const allVariablesInCallFrame = async(callFrame: SDK.DebuggerModel.CallFr
163
298
  const scopeChain = callFrame.scopeChain();
164
299
  const nameMappings = await Promise.all(scopeChain.map(resolveScope));
165
300
  const reverseMapping = new Map<string, string>();
166
- for (const map of nameMappings) {
167
- for (const [compiledName, originalName] of map) {
301
+ for (const {variableMapping} of nameMappings) {
302
+ for (const [compiledName, originalName] of variableMapping) {
168
303
  if (originalName && !reverseMapping.has(originalName)) {
169
304
  reverseMapping.set(originalName, compiledName);
170
305
  }
@@ -234,15 +369,13 @@ export const resolveThisObject =
234
369
  return callFrame.thisObject();
235
370
  }
236
371
 
237
- const namesMapping = await resolveScope(scopeChain[0]);
238
- const thisMappings = Platform.MapUtilities.inverse(namesMapping).get('this');
239
- if (!thisMappings || thisMappings.size !== 1) {
372
+ const {thisMapping} = await resolveScope(scopeChain[0]);
373
+ if (!thisMapping) {
240
374
  return callFrame.thisObject();
241
375
  }
242
376
 
243
- const [expression] = thisMappings.values();
244
377
  const result = await callFrame.evaluate(({
245
- expression,
378
+ expression: thisMapping,
246
379
  objectGroup: 'backtrace',
247
380
  includeCommandLineAPI: false,
248
381
  silent: true,
@@ -322,7 +455,7 @@ export class RemoteObject extends SDK.RemoteObject.RemoteObject {
322
455
  async getAllProperties(accessorPropertiesOnly: boolean, generatePreview: boolean):
323
456
  Promise<SDK.RemoteObject.GetPropertiesResult> {
324
457
  const allProperties = await this.object.getAllProperties(accessorPropertiesOnly, generatePreview);
325
- const namesMapping = await resolveScope(this.scope);
458
+ const {variableMapping} = await resolveScope(this.scope);
326
459
 
327
460
  const properties = allProperties.properties;
328
461
  const internalProperties = allProperties.internalProperties;
@@ -330,7 +463,7 @@ export class RemoteObject extends SDK.RemoteObject.RemoteObject {
330
463
  if (properties) {
331
464
  for (let i = 0; i < properties.length; ++i) {
332
465
  const property = properties[i];
333
- const name = namesMapping.get(property.name) || properties[i].name;
466
+ const name = variableMapping.get(property.name) || properties[i].name;
334
467
  if (!property.value) {
335
468
  continue;
336
469
  }
@@ -343,7 +476,7 @@ export class RemoteObject extends SDK.RemoteObject.RemoteObject {
343
476
  }
344
477
 
345
478
  async setPropertyValue(argumentName: string|Protocol.Runtime.CallArgument, value: string): Promise<string|undefined> {
346
- const namesMapping = await resolveScope(this.scope);
479
+ const {variableMapping} = await resolveScope(this.scope);
347
480
 
348
481
  let name;
349
482
  if (typeof argumentName === 'string') {
@@ -353,8 +486,8 @@ export class RemoteObject extends SDK.RemoteObject.RemoteObject {
353
486
  }
354
487
 
355
488
  let actualName: string = name;
356
- for (const compiledName of namesMapping.keys()) {
357
- if (namesMapping.get(compiledName) === name) {
489
+ for (const compiledName of variableMapping.keys()) {
490
+ if (variableMapping.get(compiledName) === name) {
358
491
  actualName = compiledName;
359
492
  break;
360
493
  }
@@ -127,6 +127,13 @@ export class UISourceCode extends Common.ObjectWrapper.ObjectWrapper<EventTypes>
127
127
  return this.urlInternal;
128
128
  }
129
129
 
130
+ // Identifier used for deduplicating scripts that are considered by the
131
+ // DevTools UI to be the same script. For now this is just the url but this
132
+ // is likely to change in the future.
133
+ canononicalScriptId(): string {
134
+ return this.urlInternal;
135
+ }
136
+
130
137
  parentURL(): Platform.DevToolsPath.UrlString {
131
138
  return this.parentURLInternal;
132
139
  }
@@ -96,11 +96,11 @@
96
96
  }
97
97
 
98
98
  .ax-breadcrumbs .ax-node:not(.inspected):hover {
99
- background-color: var(--color-background-elevation-0);
99
+ background-color: var(--color-background-elevation-1);
100
100
  }
101
101
 
102
102
  .ax-breadcrumbs .ax-node:not(.inspected):focus {
103
- background-color: var(--color-background-elevation-1);
103
+ background-color: var(--color-background-elevation-2);
104
104
  }
105
105
 
106
106
  .ax-breadcrumbs .ax-node.inspected:focus * {
@@ -94,6 +94,10 @@ const UIStrings = {
94
94
  * @description Link Text about explanation of back/forward cache
95
95
  */
96
96
  learnMore: 'Learn more: back/forward cache eligibility',
97
+ /**
98
+ * @description Link Text about unload handler
99
+ */
100
+ neverUseUnload: 'Learn more: Never use unload handler',
97
101
  /**
98
102
  * @description Explanation for 'pending support' items which prevent the page from being eligible
99
103
  * for back/forward cache.
@@ -536,6 +540,17 @@ export class BackForwardCacheView extends HTMLElement {
536
540
  `;
537
541
  }
538
542
 
543
+ #maybeRenderDeepLinkToUnload(explanation: Protocol.Page.BackForwardCacheNotRestoredExplanation): LitHtml.LitTemplate {
544
+ if (explanation.reason === Protocol.Page.BackForwardCacheNotRestoredReason.UnloadHandlerExistsInMainFrame ||
545
+ explanation.reason === Protocol.Page.BackForwardCacheNotRestoredReason.UnloadHandlerExistsInSubFrame) {
546
+ return LitHtml.html`
547
+ <x-link href="https://web.dev/bfcache/#never-use-the-unload-event" class="link">
548
+ ${i18nString(UIStrings.neverUseUnload)}
549
+ </x-link>`;
550
+ }
551
+ return LitHtml.nothing;
552
+ }
553
+
539
554
  #renderReason(explanation: Protocol.Page.BackForwardCacheNotRestoredExplanation, frames: string[]|undefined):
540
555
  LitHtml.TemplateResult {
541
556
  // clang-format off
@@ -554,6 +569,7 @@ export class BackForwardCacheView extends HTMLElement {
554
569
  </div>
555
570
  <div>
556
571
  ${NotRestoredReasonDescription[explanation.reason].name()}
572
+ ${this.#maybeRenderDeepLinkToUnload(explanation)}
557
573
  ${this.#maybeRenderReasonContext(explanation)}
558
574
  </div>` :
559
575
  LitHtml.nothing}
@@ -7,7 +7,7 @@ import type * as Common from '../../core/common/common.js';
7
7
  import * as i18n from '../../core/i18n/i18n.js';
8
8
  import * as UI from '../../ui/legacy/legacy.js';
9
9
 
10
- import type {LighthouseController} from './LighthouseController.js';
10
+ import type {LighthouseController, Preset} from './LighthouseController.js';
11
11
  import {Events, Presets, RuntimeSettings} from './LighthouseController.js';
12
12
  import {RadioSetting} from './RadioSetting.js';
13
13
 
@@ -42,10 +42,11 @@ const str_ = i18n.i18n.registerUIStrings('panels/lighthouse/LighthouseStartView.
42
42
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
43
43
  export class StartView extends UI.Widget.Widget {
44
44
  protected controller: LighthouseController;
45
- private readonly settingsToolbarInternal: UI.Toolbar.Toolbar;
45
+ protected readonly settingsToolbarInternal: UI.Toolbar.Toolbar;
46
46
  protected startButton!: HTMLButtonElement;
47
47
  protected helpText?: Element;
48
48
  protected warningText?: Element;
49
+ protected checkboxes: Array<{preset: Preset, checkbox: UI.Toolbar.ToolbarCheckbox}> = [];
49
50
  private shouldConfirm?: boolean;
50
51
 
51
52
  constructor(controller: LighthouseController) {
@@ -84,7 +85,7 @@ export class StartView extends UI.Widget.Widget {
84
85
  UI.ARIAUtils.setAccessibleName(control.element, label);
85
86
  }
86
87
 
87
- private populateRuntimeSettingAsToolbarCheckbox(settingName: string, toolbar: UI.Toolbar.Toolbar): void {
88
+ protected populateRuntimeSettingAsToolbarCheckbox(settingName: string, toolbar: UI.Toolbar.Toolbar): void {
88
89
  const runtimeSetting = RuntimeSettings.find(item => item.setting.name === settingName);
89
90
  if (!runtimeSetting || !runtimeSetting.title) {
90
91
  throw new Error(`${settingName} is not a setting with a title`);
@@ -109,15 +110,16 @@ export class StartView extends UI.Widget.Widget {
109
110
 
110
111
  // Populate the categories
111
112
  const categoryFormElements = fragment.$('categories-form-elements') as HTMLElement;
112
- categoryFormElements.textContent = '';
113
113
  const pluginFormElements = fragment.$('plugins-form-elements') as HTMLElement;
114
- pluginFormElements.textContent = '';
114
+
115
+ this.checkboxes = [];
115
116
  for (const preset of Presets) {
116
117
  const formElements = preset.plugin ? pluginFormElements : categoryFormElements;
117
118
  preset.setting.setTitle(preset.title());
118
119
  const checkbox = new UI.Toolbar.ToolbarSettingCheckbox(preset.setting, preset.description());
119
120
  const row = formElements.createChild('div', 'vbox lighthouse-launcher-row');
120
121
  row.appendChild(checkbox.element);
122
+ this.checkboxes.push({preset, checkbox});
121
123
  if (mode && !preset.supportedModes.includes(mode)) {
122
124
  checkbox.setEnabled(false);
123
125
  checkbox.setIndeterminate(true);
@@ -44,60 +44,22 @@ const str_ = i18n.i18n.registerUIStrings('panels/lighthouse/LighthouseStartViewF
44
44
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
45
45
 
46
46
  export class StartViewFR extends StartView {
47
- protected render(): void {
48
- super.render();
49
- this.refresh();
50
- }
51
-
52
- private createStartButton(mode: string): HTMLButtonElement {
53
- let buttonLabel: Platform.UIString.LocalizedString;
54
- let callback: () => void;
47
+ changeFormMode?: (mode: string) => void;
55
48
 
56
- if (mode === 'timespan') {
57
- buttonLabel = i18nString(UIStrings.startTimespan);
58
- callback = (): void => {
59
- this.controller.dispatchEventToListeners(
60
- Events.RequestLighthouseTimespanStart,
61
- /* keyboardInitiated */ this.startButton.matches(':focus-visible'),
62
- );
63
- };
64
- } else if (mode === 'snapshot') {
65
- buttonLabel = i18nString(UIStrings.analyzeSnapshot);
66
- callback = (): void => {
67
- this.controller.dispatchEventToListeners(
68
- Events.RequestLighthouseStart,
69
- /* keyboardInitiated */ this.startButton.matches(':focus-visible'),
70
- );
71
- };
72
- } else {
73
- buttonLabel = i18nString(UIStrings.analyzeNavigation);
74
- callback = (): void => {
75
- this.controller.dispatchEventToListeners(
76
- Events.RequestLighthouseStart,
77
- /* keyboardInitiated */ this.startButton.matches(':focus-visible'),
78
- );
79
- };
80
- }
81
-
82
- return UI.UIUtils.createTextButton(
83
- buttonLabel,
84
- callback,
85
- /* className */ '',
86
- /* primary */ true,
87
- );
88
- }
49
+ protected render(): void {
50
+ this.populateRuntimeSettingAsToolbarCheckbox('lighthouse.legacy_navigation', this.settingsToolbarInternal);
51
+ this.populateRuntimeSettingAsToolbarCheckbox('lighthouse.clear_storage', this.settingsToolbarInternal);
52
+ this.populateRuntimeSettingAsToolbarCheckbox('lighthouse.throttling', this.settingsToolbarInternal);
89
53
 
90
- refresh(): void {
91
54
  const {mode} = this.controller.getFlags();
92
-
93
- this.startButton = this.createStartButton(mode);
55
+ this.populateStartButton(mode);
94
56
 
95
57
  const fragment = UI.Fragment.Fragment.build`
96
58
  <form class="lighthouse-start-view-fr">
97
59
  <header class="hbox">
98
60
  <div class="lighthouse-logo"></div>
99
61
  <div class="lighthouse-title">${i18nString(UIStrings.generateLighthouseReport)}</div>
100
- <div class="lighthouse-start-button-container">${this.startButton}</div>
62
+ <div class="lighthouse-start-button-container" $="start-button-container">${this.startButton}</div>
101
63
  </header>
102
64
  <div $="help-text" class="lighthouse-help-text hidden"></div>
103
65
  <div class="lighthouse-options hbox">
@@ -127,16 +89,75 @@ export class StartViewFR extends StartView {
127
89
  this.helpText = fragment.$('help-text');
128
90
  this.warningText = fragment.$('warning-text');
129
91
 
130
- // The previous radios are removed later and don't exist on the new fragment yet.
131
- this.populateFormControls(fragment, mode);
132
-
133
- // Populate the Lighthouse mode
134
92
  const modeFormElements = fragment.$('mode-form-elements');
135
93
  this.populateRuntimeSettingAsRadio('lighthouse.mode', i18nString(UIStrings.mode), modeFormElements);
136
94
 
95
+ // The previous radios are removed later and don't exist on the new fragment yet.
96
+ this.populateFormControls(fragment, mode);
97
+
137
98
  this.contentElement.textContent = '';
138
99
  this.contentElement.append(fragment.element());
139
100
 
101
+ this.refresh();
102
+ }
103
+
104
+ private populateStartButton(mode: string): void {
105
+ let buttonLabel: Platform.UIString.LocalizedString;
106
+ let callback: () => void;
107
+
108
+ if (mode === 'timespan') {
109
+ buttonLabel = i18nString(UIStrings.startTimespan);
110
+ callback = (): void => {
111
+ this.controller.dispatchEventToListeners(
112
+ Events.RequestLighthouseTimespanStart,
113
+ /* keyboardInitiated */ this.startButton.matches(':focus-visible'),
114
+ );
115
+ };
116
+ } else if (mode === 'snapshot') {
117
+ buttonLabel = i18nString(UIStrings.analyzeSnapshot);
118
+ callback = (): void => {
119
+ this.controller.dispatchEventToListeners(
120
+ Events.RequestLighthouseStart,
121
+ /* keyboardInitiated */ this.startButton.matches(':focus-visible'),
122
+ );
123
+ };
124
+ } else {
125
+ buttonLabel = i18nString(UIStrings.analyzeNavigation);
126
+ callback = (): void => {
127
+ this.controller.dispatchEventToListeners(
128
+ Events.RequestLighthouseStart,
129
+ /* keyboardInitiated */ this.startButton.matches(':focus-visible'),
130
+ );
131
+ };
132
+ }
133
+
134
+ const startButtonContainer = this.contentElement.querySelector('.lighthouse-start-button-container');
135
+ if (startButtonContainer) {
136
+ startButtonContainer.textContent = '';
137
+ this.startButton = UI.UIUtils.createTextButton(
138
+ buttonLabel,
139
+ callback,
140
+ /* className */ '',
141
+ /* primary */ true,
142
+ );
143
+ startButtonContainer.append(this.startButton);
144
+ }
145
+ }
146
+
147
+ refresh(): void {
148
+ const {mode} = this.controller.getFlags();
149
+ this.populateStartButton(mode);
150
+
151
+ for (const {checkbox, preset} of this.checkboxes) {
152
+ if (preset.supportedModes.includes(mode)) {
153
+ checkbox.setEnabled(true);
154
+ checkbox.setIndeterminate(false);
155
+ } else {
156
+ checkbox.setEnabled(false);
157
+ checkbox.setIndeterminate(true);
158
+ }
159
+ }
160
+
140
161
  // Ensure the correct layout is used after refresh.
141
162
  this.onResize();
142
163
  }