chrome-devtools-frontend 1.0.1026160 → 1.0.1027447

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 (49) hide show
  1. package/config/owner/LIGHTHOUSE_OWNERS +1 -1
  2. package/docs/triage_guidelines.md +1 -122
  3. package/front_end/core/host/UserMetrics.ts +2 -1
  4. package/front_end/core/i18n/locales/en-US.json +24 -0
  5. package/front_end/core/i18n/locales/en-XL.json +24 -0
  6. package/front_end/core/root/Runtime.ts +1 -0
  7. package/front_end/core/sdk/NetworkManager.ts +24 -3
  8. package/front_end/core/sdk/ResourceTreeModel.ts +1 -1
  9. package/front_end/core/sdk/SourceMap.ts +22 -6
  10. package/front_end/core/sdk/sdk-meta.ts +7 -0
  11. package/front_end/devtools_compatibility.js +1 -0
  12. package/front_end/entrypoints/main/MainImpl.ts +5 -0
  13. package/front_end/generated/ARIAProperties.js +723 -723
  14. package/front_end/generated/SupportedCSSProperties.js +2835 -2835
  15. package/front_end/generated/protocol.ts +4 -0
  16. package/front_end/legacy_test_runner/axe_core_test_runner/axe_core_test_runner.js +1 -1
  17. package/front_end/legacy_test_runner/sources_test_runner/DebuggerTestRunner.js +1 -1
  18. package/front_end/models/bindings/CompilerScriptMapping.ts +1 -1
  19. package/front_end/models/bindings/DebuggerLanguagePlugins.ts +38 -11
  20. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +7 -1
  21. package/front_end/models/bindings/IgnoreListManager.ts +35 -22
  22. package/front_end/models/issues_manager/descriptions/clientHintMetaTagAllowListInvalidOrigin.md +1 -1
  23. package/front_end/models/issues_manager/descriptions/clientHintMetaTagModifiedHTML.md +1 -1
  24. package/front_end/models/text_utils/TextRange.ts +8 -0
  25. package/front_end/models/timeline_model/TimelineModel.ts +18 -1
  26. package/front_end/panels/accessibility/ARIAAttributesView.ts +2 -0
  27. package/front_end/panels/console/consoleView.css +4 -0
  28. package/front_end/panels/elements/ElementsTreeOutline.ts +10 -16
  29. package/front_end/panels/elements/TopLayerContainer.ts +16 -22
  30. package/front_end/panels/elements/components/ElementsBreadcrumbs.ts +45 -50
  31. package/front_end/panels/lighthouse/LighthouseController.ts +3 -0
  32. package/front_end/panels/lighthouse/LighthousePanel.ts +2 -0
  33. package/front_end/panels/security/SecurityPanel.ts +52 -0
  34. package/front_end/panels/security/originView.css +1 -1
  35. package/front_end/panels/settings/FrameworkIgnoreListSettingsTab.ts +16 -0
  36. package/front_end/panels/sources/CallStackSidebarPane.ts +2 -3
  37. package/front_end/panels/sources/DebuggerPlugin.ts +8 -2
  38. package/front_end/panels/sources/navigatorTree.css +3 -3
  39. package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +1 -1
  40. package/front_end/ui/components/docs/building-ui-documentation/CreatingComponents.md +172 -1
  41. package/front_end/ui/components/panel_feedback/PreviewToggle.ts +15 -16
  42. package/front_end/ui/components/panel_feedback/previewToggle.css +13 -15
  43. package/front_end/ui/components/text_editor/TextEditor.ts +3 -0
  44. package/front_end/ui/legacy/ARIAUtils.ts +1 -75
  45. package/front_end/ui/legacy/ListControl.ts +4 -0
  46. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +13 -3
  47. package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +5 -4
  48. package/front_end/ui/legacy/inspectorCommon.css +0 -9
  49. package/package.json +1 -1
@@ -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
  */
@@ -133,7 +133,7 @@ const DEFAULT_CONFIG = {
133
133
  // ignored by the 'aria-valid-attr' rule.
134
134
  // This should be removed after axe-core is updated.
135
135
  // See: https://github.com/dequelabs/axe-core/issues/1457
136
- {id: 'aria-valid-attr', options: ['aria-placeholder']}
136
+ {id: 'aria-valid-attr', options: ['aria-placeholder', 'aria-description']}
137
137
  ],
138
138
  runOnly: {type: 'tags', values: {include: ['wcag2a', 'best-practice'], exclude: ['experimental']}}
139
139
  };
@@ -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
  }
@@ -656,15 +656,23 @@ class SourceScopeRemoteObject extends SDK.RemoteObject.RemoteObjectImpl {
656
656
  }
657
657
 
658
658
  for (const variable of this.variables) {
659
- let sourceVar;
659
+ let sourceVar: SDK.RemoteObject.RemoteObject|undefined;
660
660
  try {
661
- sourceVar = await getValueTreeForExpression(this.#callFrame, this.#plugin, variable.name, ({
662
- generatePreview: false,
663
- includeCommandLineAPI: true,
664
- objectGroup: 'backtrace',
665
- returnByValue: false,
666
- silent: false,
667
- } as SDK.RuntimeModel.EvaluationOptions));
661
+ const evalResult = await this.#plugin.evaluate(variable.name, getRawLocation(this.#callFrame), this.stopId);
662
+ if (evalResult) {
663
+ sourceVar = new ExtensionRemoteObject(this.#callFrame, evalResult, this.#plugin);
664
+ }
665
+ // For backwards compatibility, fall back to the legacy API if the plugin doesn't define the new one.
666
+ // TODO(crbug.com/1342848) Remove
667
+ if (!sourceVar) {
668
+ sourceVar = await getValueTreeForExpression(this.#callFrame, this.#plugin, variable.name, ({
669
+ generatePreview: false,
670
+ includeCommandLineAPI: true,
671
+ objectGroup: 'backtrace',
672
+ returnByValue: false,
673
+ silent: false,
674
+ } as SDK.RuntimeModel.EvaluationOptions));
675
+ }
668
676
  } catch (e) {
669
677
  console.warn(e);
670
678
  sourceVar = new SDK.RemoteObject.LocalJSONObject(undefined);
@@ -918,7 +926,7 @@ export class DebuggerLanguagePluginManager implements
918
926
  error: string,
919
927
  }|null> {
920
928
  const {script} = callFrame;
921
- const {expression} = options;
929
+ const {expression, returnByValue, throwOnSideEffect} = options;
922
930
  const {plugin} = await this.rawModuleIdAndPluginForScript(script);
923
931
  if (!plugin) {
924
932
  return null;
@@ -929,9 +937,20 @@ export class DebuggerLanguagePluginManager implements
929
937
  return null;
930
938
  }
931
939
 
940
+ if (returnByValue) {
941
+ return {error: 'Cannot return by value'};
942
+ }
943
+ if (throwOnSideEffect) {
944
+ return {error: 'Cannot guarantee side-effect freedom'};
945
+ }
946
+
932
947
  try {
933
- const object = await getValueTreeForExpression(callFrame, plugin, expression, options);
934
- return {object, exceptionDetails: undefined};
948
+ const object = await plugin.evaluate(expression, location, this.stopIdForCallFrame(callFrame));
949
+ if (!object) {
950
+ const object = await getValueTreeForExpression(callFrame, plugin, expression, options);
951
+ return {object, exceptionDetails: undefined};
952
+ }
953
+ return {object: new ExtensionRemoteObject(callFrame, object, plugin), exceptionDetails: undefined};
935
954
  } catch (error) {
936
955
  if (error instanceof FormattingError) {
937
956
  const {exception: object, exceptionDetails} = error;
@@ -1542,4 +1561,12 @@ class ModelData {
1542
1561
  export interface DebuggerLanguagePlugin extends Chrome.DevTools.LanguageExtensionPlugin {
1543
1562
  name: string;
1544
1563
  handleScript(script: SDK.Script.Script): boolean;
1564
+
1565
+ // These are optional in the public interface for compatibility purposes, but ExtensionAPI handles the missing
1566
+ // functions gracefully, so we can mark them non-optional here.
1567
+ // TODO(crbug.com/1342848) Remove
1568
+ evaluate(expression: string, context: Chrome.DevTools.RawLocation, stopId: unknown):
1569
+ Promise<Chrome.DevTools.RemoteObject|null>;
1570
+ getProperties(objectId: Chrome.DevTools.RemoteObjectId): Promise<Chrome.DevTools.PropertyDescriptor[]>;
1571
+ releaseObject(objectId: Chrome.DevTools.RemoteObjectId): Promise<void>;
1545
1572
  }
@@ -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
 
@@ -28,6 +28,9 @@ export class IgnoreListManager implements SDK.TargetManager.SDKModelObserver<SDK
28
28
  Common.Settings.Settings.instance()
29
29
  .moduleSetting('skipContentScripts')
30
30
  .addChangeListener(this.patternChanged.bind(this));
31
+ Common.Settings.Settings.instance()
32
+ .moduleSetting('automaticallyIgnoreListKnownThirdPartyScripts')
33
+ .addChangeListener(this.patternChanged.bind(this));
31
34
 
32
35
  this.#listeners = new Set();
33
36
 
@@ -97,21 +100,33 @@ export class IgnoreListManager implements SDK.TargetManager.SDKModelObserver<SDK
97
100
  return debuggerModel.setBlackboxPatterns(patterns);
98
101
  }
99
102
 
100
- isIgnoreListedUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): boolean {
103
+ isUserOrSourceMapIgnoreListedUISourceCode(
104
+ uiSourceCode: Workspace.UISourceCode.UISourceCode, sourceMap: SDK.SourceMap.SourceMap|null): boolean {
101
105
  const projectType = uiSourceCode.project().type();
102
106
  const isContentScript = projectType === Workspace.Workspace.projectTypes.ContentScripts;
103
- if (isContentScript && Common.Settings.Settings.instance().moduleSetting('skipContentScripts').get()) {
107
+ if (this.skipContentScripts && isContentScript) {
104
108
  return true;
105
109
  }
106
110
  const url = this.uiSourceCodeURL(uiSourceCode);
107
- return url ? this.isIgnoreListedURL(url) : false;
111
+ return url ? this.isUserOrSourceMapIgnoreListedURL(url, sourceMap) : false;
112
+ }
113
+
114
+ isUserOrSourceMapIgnoreListedURL(url: Platform.DevToolsPath.UrlString, sourceMap: SDK.SourceMap.SourceMap|null):
115
+ boolean {
116
+ if (this.isUserIgnoreListedURL(url)) {
117
+ return true;
118
+ }
119
+ if (this.automaticallyIgnoreListKnownThirdPartyScripts && sourceMap?.hasIgnoreListHint(url)) {
120
+ return true;
121
+ }
122
+ return false;
108
123
  }
109
124
 
110
- isIgnoreListedURL(url: Platform.DevToolsPath.UrlString, isContentScript?: boolean): boolean {
125
+ isUserIgnoreListedURL(url: Platform.DevToolsPath.UrlString, isContentScript?: boolean): boolean {
111
126
  if (this.#isIgnoreListedURLCache.has(url)) {
112
127
  return Boolean(this.#isIgnoreListedURLCache.get(url));
113
128
  }
114
- if (isContentScript && Common.Settings.Settings.instance().moduleSetting('skipContentScripts').get()) {
129
+ if (isContentScript && this.skipContentScripts) {
115
130
  return true;
116
131
  }
117
132
  const regex = this.getSkipStackFramesPatternSetting().asRegExp();
@@ -137,8 +152,9 @@ export class IgnoreListManager implements SDK.TargetManager.SDKModelObserver<SDK
137
152
 
138
153
  private async updateScriptRanges(script: SDK.Script.Script, sourceMap: SDK.SourceMap.SourceMap|null): Promise<void> {
139
154
  let hasIgnoreListedMappings = false;
140
- if (!IgnoreListManager.instance().isIgnoreListedURL(script.sourceURL, script.isContentScript())) {
141
- hasIgnoreListedMappings = sourceMap ? sourceMap.sourceURLs().some(url => this.isIgnoreListedURL(url)) : false;
155
+ if (!IgnoreListManager.instance().isUserIgnoreListedURL(script.sourceURL, script.isContentScript())) {
156
+ hasIgnoreListedMappings =
157
+ sourceMap?.sourceURLs().some(url => this.isUserOrSourceMapIgnoreListedURL(url, sourceMap)) ?? false;
142
158
  }
143
159
  if (!hasIgnoreListedMappings) {
144
160
  if (scriptToRange.get(script) && await script.setBlackboxedRanges([])) {
@@ -152,21 +168,10 @@ export class IgnoreListManager implements SDK.TargetManager.SDKModelObserver<SDK
152
168
  return;
153
169
  }
154
170
 
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
- }
171
+ const newRanges =
172
+ sourceMap
173
+ .findRanges(srcURL => this.isUserOrSourceMapIgnoreListedURL(srcURL, sourceMap), {isStartMatching: true})
174
+ .flatMap(range => [range.start, range.end]);
170
175
 
171
176
  const oldRanges = scriptToRange.get(script) || [];
172
177
  if (!isEqual(oldRanges, newRanges) && await script.setBlackboxedRanges(newRanges)) {
@@ -210,6 +215,14 @@ export class IgnoreListManager implements SDK.TargetManager.SDKModelObserver<SDK
210
215
  }
211
216
  }
212
217
 
218
+ get skipContentScripts(): boolean {
219
+ return Common.Settings.Settings.instance().moduleSetting('skipContentScripts').get();
220
+ }
221
+
222
+ get automaticallyIgnoreListKnownThirdPartyScripts(): boolean {
223
+ return Common.Settings.Settings.instance().moduleSetting('automaticallyIgnoreListKnownThirdPartyScripts').get();
224
+ }
225
+
213
226
  ignoreListContentScripts(): void {
214
227
  Common.Settings.Settings.instance().moduleSetting('skipContentScripts').set(true);
215
228
  }
@@ -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 = {
@@ -229,6 +229,7 @@ const ATTRIBUTES = new Set<string>([
229
229
  'aria-activedescendant',
230
230
  'aria-atomic',
231
231
  'aria-autocomplete',
232
+ 'aria-braillelabel',
232
233
  'aria-brailleroledescription',
233
234
  'aria-busy',
234
235
  'aria-checked',
@@ -239,6 +240,7 @@ const ATTRIBUTES = new Set<string>([
239
240
  'aria-controls',
240
241
  'aria-current',
241
242
  'aria-describedby',
243
+ 'aria-description',
242
244
  'aria-details',
243
245
  'aria-disabled',
244
246
  'aria-dropeffect',
@@ -546,3 +546,7 @@
546
546
  --override-error-text-color: HighlightText;
547
547
  }
548
548
  }
549
+
550
+ [slot="insertion-point-sidebar"] {
551
+ contain: layout style;
552
+ }
@@ -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
  }
@@ -45,6 +45,7 @@ export class ElementsBreadcrumbs extends HTMLElement {
45
45
  static readonly litTagName = LitHtml.literal`devtools-elements-breadcrumbs`;
46
46
  readonly #shadow = this.attachShadow({mode: 'open'});
47
47
  readonly #resizeObserver = new ResizeObserver(() => this.#checkForOverflowOnResize());
48
+ readonly #renderBound = this.#render.bind(this);
48
49
 
49
50
  #crumbsData: readonly DOMNode[] = [];
50
51
  #selectedDOMNode: Readonly<DOMNode>|null = null;
@@ -61,7 +62,7 @@ export class ElementsBreadcrumbs extends HTMLElement {
61
62
  this.#selectedDOMNode = data.selectedNode;
62
63
  this.#crumbsData = data.crumbs;
63
64
  this.#userHasManuallyScrolled = false;
64
- void this.#update();
65
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#renderBound);
65
66
  }
66
67
 
67
68
  disconnectedCallback(): void {
@@ -104,12 +105,6 @@ export class ElementsBreadcrumbs extends HTMLElement {
104
105
  }
105
106
  }
106
107
 
107
- async #update(): Promise<void> {
108
- await this.#render();
109
- this.#engageResizeObserver();
110
- void this.#ensureSelectedNodeIsVisible();
111
- }
112
-
113
108
  #onCrumbMouseMove(node: DOMNode): () => void {
114
109
  return (): void => node.highlightNode();
115
110
  }
@@ -267,53 +262,53 @@ export class ElementsBreadcrumbs extends HTMLElement {
267
262
  `;
268
263
  }
269
264
 
270
- async #render(): Promise<void> {
265
+ #render(): void {
271
266
  const crumbs = crumbsToRender(this.#crumbsData, this.#selectedDOMNode);
272
267
 
273
- await coordinator.write('Breadcrumbs render', () => {
274
- // Disabled until https://crbug.com/1079231 is fixed.
275
- // clang-format off
276
- LitHtml.render(LitHtml.html`
277
- <nav class="crumbs" aria-label=${i18nString(UIStrings.breadcrumbs)}>
278
- ${this.#renderOverflowButton('left', this.#userScrollPosition === 'start')}
279
-
280
- <div class="crumbs-window" @scroll=${this.#onCrumbsWindowScroll}>
281
- <ul class="crumbs-scroll-container">
282
- ${crumbs.map(crumb => {
283
- const crumbClasses = {
284
- crumb: true,
285
- selected: crumb.selected,
286
- };
287
- // eslint-disable-next-line rulesdir/ban_a_tags_in_lit_html
288
- return LitHtml.html`
289
- <li class=${LitHtml.Directives.classMap(crumbClasses)}
290
- data-node-id=${crumb.node.id}
291
- data-crumb="true"
292
- >
293
- <a href="#"
294
- draggable=false
295
- class="crumb-link"
296
- @click=${this.#onCrumbClick(crumb.node)}
297
- @mousemove=${this.#onCrumbMouseMove(crumb.node)}
298
- @mouseleave=${this.#onCrumbMouseLeave(crumb.node)}
299
- @focus=${this.#onCrumbFocus(crumb.node)}
300
- @blur=${this.#onCrumbBlur(crumb.node)}
301
- ><${NodeText.NodeText.NodeText.litTagName} data-node-title=${crumb.title.main} .data=${{
302
- nodeTitle: crumb.title.main,
303
- nodeId: crumb.title.extras.id,
304
- nodeClasses: crumb.title.extras.classes,
305
- } as NodeText.NodeText.NodeTextData}></${NodeText.NodeText.NodeText.litTagName}></a>
306
- </li>`;
307
- })}
308
- </ul>
309
- </div>
310
- ${this.#renderOverflowButton('right', this.#userScrollPosition === 'end')}
311
- </nav>
312
- `, this.#shadow, { host: this });
313
- // clang-format on
314
- });
268
+ // Disabled until https://crbug.com/1079231 is fixed.
269
+ // clang-format off
270
+ LitHtml.render(LitHtml.html`
271
+ <nav class="crumbs" aria-label=${i18nString(UIStrings.breadcrumbs)}>
272
+ ${this.#renderOverflowButton('left', this.#userScrollPosition === 'start')}
273
+
274
+ <div class="crumbs-window" @scroll=${this.#onCrumbsWindowScroll}>
275
+ <ul class="crumbs-scroll-container">
276
+ ${crumbs.map(crumb => {
277
+ const crumbClasses = {
278
+ crumb: true,
279
+ selected: crumb.selected,
280
+ };
281
+ // eslint-disable-next-line rulesdir/ban_a_tags_in_lit_html
282
+ return LitHtml.html`
283
+ <li class=${LitHtml.Directives.classMap(crumbClasses)}
284
+ data-node-id=${crumb.node.id}
285
+ data-crumb="true"
286
+ >
287
+ <a href="#"
288
+ draggable=false
289
+ class="crumb-link"
290
+ @click=${this.#onCrumbClick(crumb.node)}
291
+ @mousemove=${this.#onCrumbMouseMove(crumb.node)}
292
+ @mouseleave=${this.#onCrumbMouseLeave(crumb.node)}
293
+ @focus=${this.#onCrumbFocus(crumb.node)}
294
+ @blur=${this.#onCrumbBlur(crumb.node)}
295
+ ><${NodeText.NodeText.NodeText.litTagName} data-node-title=${crumb.title.main} .data=${{
296
+ nodeTitle: crumb.title.main,
297
+ nodeId: crumb.title.extras.id,
298
+ nodeClasses: crumb.title.extras.classes,
299
+ } as NodeText.NodeText.NodeTextData}></${NodeText.NodeText.NodeText.litTagName}></a>
300
+ </li>`;
301
+ })}
302
+ </ul>
303
+ </div>
304
+ ${this.#renderOverflowButton('right', this.#userScrollPosition === 'end')}
305
+ </nav>
306
+ `, this.#shadow, { host: this });
307
+ // clang-format on
315
308
 
316
309
  void this.#checkForOverflow();
310
+ this.#engageResizeObserver();
311
+ void this.#ensureSelectedNodeIsVisible();
317
312
  }
318
313
 
319
314
  async #ensureSelectedNodeIsVisible(): Promise<void> {
@@ -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)
@@ -308,6 +308,8 @@ export class LighthousePanel extends UI.Panel.Panel {
308
308
  const reportContainer = this.auditResultsElement.createChild('div', 'lh-vars lh-root lh-devtools');
309
309
  // @ts-ignore Expose LHR on DOM for e2e tests
310
310
  reportContainer._lighthouseResultForTesting = lighthouseResult;
311
+ // @ts-ignore Expose Artifacts on DOM for e2e tests
312
+ reportContainer._lighthouseArtifactsForTesting = artifacts;
311
313
 
312
314
  const dom = new LighthouseReport.DOM(this.auditResultsElement.ownerDocument as Document, reportContainer);
313
315
  const renderer = new LighthouseReportRenderer(dom) as LighthouseReport.ReportRenderer;