chrome-devtools-frontend 1.0.1035963 → 1.0.1037221

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 (25) hide show
  1. package/AUTHORS +1 -0
  2. package/front_end/core/sdk/CSSStyleDeclaration.ts +1 -1
  3. package/front_end/core/sdk/NetworkManager.ts +21 -5
  4. package/front_end/core/sdk/NetworkRequest.ts +10 -0
  5. package/front_end/entrypoints/main/MainImpl.ts +2 -1
  6. package/front_end/models/persistence/NetworkPersistenceManager.ts +25 -14
  7. package/front_end/panels/elements/CSSRuleValidator.ts +1 -1
  8. package/front_end/panels/elements/ElementsTreeOutline.ts +3 -3
  9. package/front_end/panels/elements/StylePropertiesSection.ts +12 -0
  10. package/front_end/panels/elements/StylePropertyTreeElement.ts +1 -1
  11. package/front_end/panels/elements/StylesSidebarPane.ts +1 -0
  12. package/front_end/panels/elements/TopLayerContainer.ts +14 -13
  13. package/front_end/panels/media/PlayerPropertiesView.ts +43 -11
  14. package/front_end/panels/media/playerPropertiesView.css +22 -3
  15. package/front_end/panels/network/NetworkLogView.ts +13 -5
  16. package/front_end/panels/sources/NavigatorView.ts +8 -3
  17. package/front_end/panels/sources/components/BreakpointsView.ts +11 -16
  18. package/front_end/panels/sources/components/HeadersView.ts +8 -41
  19. package/front_end/panels/sources/components/breakpointsView.css +21 -13
  20. package/front_end/panels/timeline/TimelineLoader.ts +1 -1
  21. package/front_end/ui/components/linear_memory_inspector/LinearMemoryHighlightChipList.ts +11 -9
  22. package/front_end/ui/components/linear_memory_inspector/linearMemoryHighlightChipList.css +48 -30
  23. package/front_end/ui/components/text_prompt/TextPrompt.ts +1 -1
  24. package/front_end/ui/legacy/components/quick_open/FilteredListWidget.ts +1 -1
  25. package/package.json +1 -1
package/AUTHORS CHANGED
@@ -17,6 +17,7 @@ Alexey Rodionov <fluorescent.hallucinogen@gmail.com>
17
17
  Ankit Mishra <ankit.mishra131990@gmail.com>
18
18
  Anna Agoha <annaagoha@gmail.com>
19
19
  Anthony Xie <anthonyxie64@gmail.com>
20
+ Boris Verkhovskiy <boris.verk@gmail.com>
20
21
  Carl Espe <carl@cpespe.com>
21
22
  Conner Turner <cturner@zyme.xyz>
22
23
  Daniel bellfield <dnlbellfield@gmail.com>
@@ -184,7 +184,7 @@ export class CSSStyleDeclaration {
184
184
  for (const shorthand of shorthands) {
185
185
  if (propertiesSet.has(shorthand)) {
186
186
  continue;
187
- } // There already is a shorthand this #longhands falls under.
187
+ } // There already is a shorthand this #longhand falls under.
188
188
  const shorthandValue = this.#shorthandValues.get(shorthand);
189
189
  if (!shorthandValue) {
190
190
  continue;
@@ -141,7 +141,7 @@ export class NetworkManager extends SDKModel<EventTypes> {
141
141
  constructor(target: Target) {
142
142
  super(target);
143
143
  this.dispatcher = new NetworkDispatcher(this);
144
- this.fetchDispatcher = new FetchDispatcher(target.fetchAgent());
144
+ this.fetchDispatcher = new FetchDispatcher(target.fetchAgent(), this);
145
145
  this.#networkAgent = target.networkAgent();
146
146
  target.registerNetworkDispatcher(this.dispatcher);
147
147
  target.registerFetchDispatcher(this.fetchDispatcher);
@@ -264,7 +264,7 @@ export class NetworkManager extends SDKModel<EventTypes> {
264
264
  return this.dispatcher.requestForURL(url);
265
265
  }
266
266
 
267
- requestforId(id: string): NetworkRequest|null {
267
+ requestForId(id: string): NetworkRequest|null {
268
268
  return this.dispatcher.requestForId(id);
269
269
  }
270
270
 
@@ -392,15 +392,18 @@ const MAX_EAGER_POST_REQUEST_BODY_LENGTH = 64 * 1024; // bytes
392
392
 
393
393
  export class FetchDispatcher implements ProtocolProxyApi.FetchDispatcher {
394
394
  readonly #fetchAgent: ProtocolProxyApi.FetchApi;
395
+ readonly #manager: NetworkManager;
395
396
 
396
- constructor(agent: ProtocolProxyApi.FetchApi) {
397
+ constructor(agent: ProtocolProxyApi.FetchApi, manager: NetworkManager) {
397
398
  this.#fetchAgent = agent;
399
+ this.#manager = manager;
398
400
  }
399
401
 
400
- requestPaused({requestId, request, resourceType, responseStatusCode, responseHeaders}:
402
+ requestPaused({requestId, request, resourceType, responseStatusCode, responseHeaders, networkId}:
401
403
  Protocol.Fetch.RequestPausedEvent): void {
404
+ const networkRequest = networkId ? this.#manager.requestForId(networkId) : null;
402
405
  void MultitargetNetworkManager.instance().requestIntercepted(new InterceptedRequest(
403
- this.#fetchAgent, request, resourceType, requestId, responseStatusCode, responseHeaders));
406
+ this.#fetchAgent, request, resourceType, requestId, networkRequest, responseStatusCode, responseHeaders));
404
407
  }
405
408
 
406
409
  authRequired({}: Protocol.Fetch.AuthRequiredEvent): void {
@@ -1549,12 +1552,14 @@ export class InterceptedRequest {
1549
1552
  responseStatusCode: number|undefined;
1550
1553
  responseHeaders: Protocol.Fetch.HeaderEntry[]|undefined;
1551
1554
  requestId: Protocol.Fetch.RequestId;
1555
+ networkRequest: NetworkRequest|null;
1552
1556
 
1553
1557
  constructor(
1554
1558
  fetchAgent: ProtocolProxyApi.FetchApi,
1555
1559
  request: Protocol.Network.Request,
1556
1560
  resourceType: Protocol.Network.ResourceType,
1557
1561
  requestId: Protocol.Fetch.RequestId,
1562
+ networkRequest: NetworkRequest|null,
1558
1563
  responseStatusCode?: number,
1559
1564
  responseHeaders?: Protocol.Fetch.HeaderEntry[],
1560
1565
  ) {
@@ -1565,6 +1570,17 @@ export class InterceptedRequest {
1565
1570
  this.responseStatusCode = responseStatusCode;
1566
1571
  this.responseHeaders = responseHeaders;
1567
1572
  this.requestId = requestId;
1573
+ this.networkRequest = networkRequest;
1574
+ if (this.networkRequest && this.responseHeaders) {
1575
+ // This populates 'NetworkRequest.originalResponseHeaders' with the
1576
+ // response headers from CDP's 'Fetch.requestPaused'. Populating this
1577
+ // field together with 'NetworkRequest.responseHeaders' with the info
1578
+ // from 'Network.responseReceivedExtraInfo' would have been easier, but we
1579
+ // are not sure whether the response headers from the 2 CDP events are
1580
+ // always exactly the same.
1581
+ // Creates a deep copy.
1582
+ this.networkRequest.originalResponseHeaders = this.responseHeaders.map(headerEntry => ({...headerEntry}));
1583
+ }
1568
1584
  }
1569
1585
 
1570
1586
  hasResponded(): boolean {
@@ -236,6 +236,7 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
236
236
  [x: string]: string|undefined,
237
237
  };
238
238
  #responseHeadersTextInternal: string;
239
+ #originalResponseHeaders: Protocol.Fetch.HeaderEntry[];
239
240
  #requestHeadersInternal: NameValue[];
240
241
  #requestHeaderValues: {
241
242
  [x: string]: string|undefined,
@@ -336,6 +337,7 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
336
337
 
337
338
  this.#responseHeaderValues = {};
338
339
  this.#responseHeadersTextInternal = '';
340
+ this.#originalResponseHeaders = [];
339
341
 
340
342
  this.#requestHeadersInternal = [];
341
343
  this.#requestHeaderValues = {};
@@ -939,6 +941,14 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
939
941
  this.dispatchEventToListeners(Events.ResponseHeadersChanged);
940
942
  }
941
943
 
944
+ get originalResponseHeaders(): Protocol.Fetch.HeaderEntry[] {
945
+ return this.#originalResponseHeaders;
946
+ }
947
+
948
+ set originalResponseHeaders(headers: Protocol.Fetch.HeaderEntry[]) {
949
+ this.#originalResponseHeaders = headers;
950
+ }
951
+
942
952
  get responseHeadersText(): string {
943
953
  return this.#responseHeadersTextInternal;
944
954
  }
@@ -298,7 +298,7 @@ export class MainImpl {
298
298
  'https://developer.chrome.com/blog/new-in-devtools-92/#source-order');
299
299
  Root.Runtime.experiments.register('webauthnPane', 'WebAuthn Pane');
300
300
  Root.Runtime.experiments.register(
301
- 'keyboardShortcutEditor', 'Enable keyboard shortcut editor', true,
301
+ 'keyboardShortcutEditor', 'Enable keyboard shortcut editor', false,
302
302
  'https://developer.chrome.com/blog/new-in-devtools-88/#keyboard-shortcuts');
303
303
 
304
304
  // Back/forward cache
@@ -429,6 +429,7 @@ export class MainImpl {
429
429
  Root.Runtime.ExperimentName.CSS_LAYERS,
430
430
  ...('EyeDropper' in window ? [Root.Runtime.ExperimentName.EYEDROPPER_COLOR_PICKER] : []),
431
431
  'lighthousePanelFR',
432
+ 'keyboardShortcutEditor',
432
433
  ]);
433
434
 
434
435
  Root.Runtime.experiments.setNonConfigurableExperiments([
@@ -622,19 +622,27 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
622
622
  }
623
623
  }
624
624
 
625
- mergeHeaders(baseHeaders: Protocol.Fetch.HeaderEntry[], overrideHeaders: Protocol.Network.Headers):
625
+ mergeHeaders(baseHeaders: Protocol.Fetch.HeaderEntry[], overrideHeaders: Protocol.Fetch.HeaderEntry[]):
626
626
  Protocol.Fetch.HeaderEntry[] {
627
- const result: Protocol.Fetch.HeaderEntry[] = [];
628
- const headerMap = new Map<string, string>();
629
- for (const header of baseHeaders) {
630
- headerMap.set(header.name, header.value);
627
+ const headerMap = new Platform.MapUtilities.Multimap<string, string>();
628
+ for (const {name, value} of overrideHeaders) {
629
+ headerMap.set(name.toLowerCase(), value);
631
630
  }
632
- for (const [headerName, headerValue] of Object.entries(overrideHeaders)) {
633
- headerMap.set(headerName, headerValue);
631
+
632
+ const overriddenHeaderNames = new Set(headerMap.keysArray());
633
+ for (const {name, value} of baseHeaders) {
634
+ const lowerCaseName = name.toLowerCase();
635
+ if (!overriddenHeaderNames.has(lowerCaseName)) {
636
+ headerMap.set(lowerCaseName, value);
637
+ }
638
+ }
639
+
640
+ const result: Protocol.Fetch.HeaderEntry[] = [];
641
+ for (const headerName of headerMap.keysArray()) {
642
+ for (const headerValue of headerMap.get(headerName)) {
643
+ result.push({name: headerName, value: headerValue});
644
+ }
634
645
  }
635
- headerMap.forEach((headerValue, headerName) => {
636
- result.push({name: headerName, value: headerValue});
637
- });
638
646
  return result;
639
647
  }
640
648
 
@@ -766,20 +774,23 @@ export type EventTypes = {
766
774
 
767
775
  export interface HeaderOverride {
768
776
  applyTo: string;
769
- headers: Protocol.Network.Headers;
777
+ headers: Protocol.Fetch.HeaderEntry[];
770
778
  }
771
779
 
772
780
  interface HeaderOverrideWithRegex {
773
781
  applyToRegex: RegExp;
774
- headers: Protocol.Network.Headers;
782
+ headers: Protocol.Fetch.HeaderEntry[];
775
783
  }
776
784
 
777
785
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
778
786
  export function isHeaderOverride(arg: any): arg is HeaderOverride {
779
- if (!(arg && arg.applyTo && typeof (arg.applyTo === 'string') && arg.headers && Object.keys(arg.headers).length)) {
787
+ if (!(arg && arg.applyTo && typeof arg.applyTo === 'string' && arg.headers && arg.headers.length &&
788
+ Array.isArray(arg.headers))) {
780
789
  return false;
781
790
  }
782
- return Object.values(arg.headers).every(value => typeof value === 'string');
791
+ return arg.headers.every(
792
+ (header: Protocol.Fetch.HeaderEntry) =>
793
+ header.name && typeof header.name === 'string' && header.value && typeof header.value === 'string');
783
794
  }
784
795
 
785
796
  export function escapeRegex(pattern: string): string {
@@ -142,7 +142,7 @@ export class AlignContentValidator extends CSSRuleValidator {
142
142
  if (this.#isRuleValid(computedStyles)) {
143
143
  return;
144
144
  }
145
- const reasonPropertyDeclaration = buildPropertyText('flex-wrap');
145
+ const reasonPropertyDeclaration = buildPropertyDefinitionText('flex-wrap', 'nowrap');
146
146
  const affectedPropertyDeclarationCode = buildPropertyText('align-content');
147
147
 
148
148
  return new Hint(
@@ -1189,7 +1189,7 @@ export class ElementsTreeOutline extends
1189
1189
  }
1190
1190
  const container = new TopLayerContainer(parent.treeOutline, domModel);
1191
1191
  await container.throttledUpdateTopLayerElements();
1192
- if (container.currentTopLayerElements.size > 0) {
1192
+ if (container.currentTopLayerDOMNodes.size > 0) {
1193
1193
  parent.appendChild(container);
1194
1194
  }
1195
1195
  this.#topLayerContainerByParent.set(parent, container);
@@ -1460,10 +1460,10 @@ export class ElementsTreeOutline extends
1460
1460
  private async topLayerElementsChanged(): Promise<void> {
1461
1461
  for (const [parent, container] of this.#topLayerContainerByParent) {
1462
1462
  await container.throttledUpdateTopLayerElements();
1463
- if (container.currentTopLayerElements.size > 0 && container.parent !== parent) {
1463
+ if (container.currentTopLayerDOMNodes.size > 0 && container.parent !== parent) {
1464
1464
  parent.appendChild(container);
1465
1465
  }
1466
- container.hidden = container.currentTopLayerElements.size === 0;
1466
+ container.hidden = container.currentTopLayerDOMNodes.size === 0;
1467
1467
  }
1468
1468
  }
1469
1469
 
@@ -298,6 +298,18 @@ export class StylePropertiesSection {
298
298
  this.parentsComputedStyles = parentsComputedStyles;
299
299
  }
300
300
 
301
+ updateAuthoringHint(): void {
302
+ let child = this.propertiesTreeOutline.firstChild();
303
+ while (child) {
304
+ if (child instanceof StylePropertyTreeElement) {
305
+ child.setComputedStyles(this.computedStyles);
306
+ child.setParentsComputedStyles(this.parentsComputedStyles);
307
+ child.updateAuthoringHint();
308
+ }
309
+ child = child.nextSibling;
310
+ }
311
+ }
312
+
301
313
  setSectionIdx(sectionIdx: number): void {
302
314
  this.sectionIdx = sectionIdx;
303
315
  this.onpopulate();
@@ -761,7 +761,7 @@ export class StylePropertyTreeElement extends UI.TreeOutline.TreeElement {
761
761
  }
762
762
  }
763
763
 
764
- private updateAuthoringHint(): void {
764
+ updateAuthoringHint(): void {
765
765
  if (!Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.CSS_AUTHORING_HINTS)) {
766
766
  return;
767
767
  }
@@ -785,6 +785,7 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
785
785
  for (const section of this.allSections()) {
786
786
  section.setComputedStyles(computedStyles);
787
787
  section.setParentsComputedStyles(parentsComputedStyles);
788
+ section.updateAuthoringHint();
788
789
  }
789
790
  }
790
791
 
@@ -25,14 +25,13 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
25
25
  export class TopLayerContainer extends UI.TreeOutline.TreeElement {
26
26
  tree: ElementsTreeOutline.ElementsTreeOutline;
27
27
  domModel: SDK.DOMModel.DOMModel;
28
- currentTopLayerElements: Set<ElementsTreeElement>;
28
+ currentTopLayerDOMNodes: Set<SDK.DOMModel.DOMNode> = new Set();
29
29
  topLayerUpdateThrottler: Common.Throttler.Throttler;
30
30
 
31
31
  constructor(tree: ElementsTreeOutline.ElementsTreeOutline, domModel: SDK.DOMModel.DOMModel) {
32
32
  super('#top-layer');
33
33
  this.tree = tree;
34
34
  this.domModel = domModel;
35
- this.currentTopLayerElements = new Set();
36
35
  this.topLayerUpdateThrottler = new Common.Throttler.Throttler(1);
37
36
  }
38
37
 
@@ -43,7 +42,7 @@ export class TopLayerContainer extends UI.TreeOutline.TreeElement {
43
42
  async updateTopLayerElements(): Promise<void> {
44
43
  this.removeChildren();
45
44
  this.removeCurrentTopLayerElementsAdorners();
46
- this.currentTopLayerElements = new Set();
45
+ this.currentTopLayerDOMNodes = new Set();
47
46
 
48
47
  const newTopLayerElementsIDs = await this.domModel.getTopLayerElements();
49
48
  if (!newTopLayerElementsIDs || newTopLayerElementsIDs.length === 0) {
@@ -57,15 +56,9 @@ export class TopLayerContainer extends UI.TreeOutline.TreeElement {
57
56
  const topLayerElementShortcut = new SDK.DOMModel.DOMNodeShortcut(
58
57
  this.domModel.target(), topLayerDOMNode.backendNodeId(), 0, topLayerDOMNode.nodeName());
59
58
  const topLayerElementRepresentation = new ElementsTreeOutline.ShortcutTreeElement(topLayerElementShortcut);
60
- const topLayerTreeElement = this.tree.treeElementByNode.get(topLayerDOMNode);
61
- if (!topLayerTreeElement) {
62
- continue;
63
- }
64
-
65
- topLayerElementIndex++;
66
- this.addTopLayerAdorner(topLayerTreeElement, topLayerElementRepresentation, topLayerElementIndex);
67
- this.currentTopLayerElements.add(topLayerTreeElement);
68
59
  this.appendChild(topLayerElementRepresentation);
60
+ this.currentTopLayerDOMNodes.add(topLayerDOMNode);
61
+
69
62
  // Add the element's backdrop if previous top layer element is a backdrop.
70
63
  const previousTopLayerDOMNode =
71
64
  (i > 0) ? this.domModel.idToDOMNode.get(newTopLayerElementsIDs[i - 1]) : undefined;
@@ -75,13 +68,21 @@ export class TopLayerContainer extends UI.TreeOutline.TreeElement {
75
68
  const backdropElementRepresentation = new ElementsTreeOutline.ShortcutTreeElement(backdropElementShortcut);
76
69
  topLayerElementRepresentation.appendChild(backdropElementRepresentation);
77
70
  }
71
+
72
+ // TODO(changhaohan): store not-yet-inserted DOMNodes and adorn them when inserted.
73
+ const topLayerTreeElement = this.tree.treeElementByNode.get(topLayerDOMNode);
74
+ if (topLayerTreeElement) {
75
+ this.addTopLayerAdorner(topLayerTreeElement, topLayerElementRepresentation, ++topLayerElementIndex);
76
+ }
78
77
  }
79
78
  }
80
79
  }
81
80
 
82
81
  private removeCurrentTopLayerElementsAdorners(): void {
83
- for (const topLayerElement of this.currentTopLayerElements) {
84
- topLayerElement.removeAllAdorners();
82
+ for (const node of this.currentTopLayerDOMNodes) {
83
+ const topLayerTreeElement = this.tree.treeElementByNode.get(node);
84
+ // TODO(changhaohan): remove only top layer adorner.
85
+ topLayerTreeElement?.removeAllAdorners();
85
86
  }
86
87
  }
87
88
 
@@ -4,6 +4,7 @@
4
4
 
5
5
  import * as i18n from '../../core/i18n/i18n.js';
6
6
  import * as Platform from '../../core/platform/platform.js';
7
+ import * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js';
7
8
  import * as UI from '../../ui/legacy/legacy.js';
8
9
 
9
10
  import playerPropertiesViewStyles from './playerPropertiesView.css.js';
@@ -136,7 +137,7 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
136
137
  const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_);
137
138
 
138
139
  type TabData = {
139
- [x: string]: string,
140
+ [x: string]: string|object,
140
141
  };
141
142
 
142
143
  // Keep this enum in sync with panels/media/base/media_log_properties.h
@@ -179,7 +180,7 @@ export class PropertyRenderer extends UI.Widget.VBox {
179
180
  super();
180
181
  this.contentElement.classList.add('media-property-renderer');
181
182
  const titleElement = this.contentElement.createChild('span', 'media-property-renderer-title');
182
- this.contents = this.contentElement.createChild('span', 'media-property-renderer-contents');
183
+ this.contents = this.contentElement.createChild('div', 'media-property-renderer-contents');
183
184
  UI.UIUtils.createTextChild(titleElement, title);
184
185
  this.title = title;
185
186
  this.value = null;
@@ -213,16 +214,36 @@ export class PropertyRenderer extends UI.Widget.VBox {
213
214
  }
214
215
  }
215
216
 
217
+ protected unsetNestedContents(): void {
218
+ this.contentElement.classList.add('media-property-renderer-hidden');
219
+ if (this.pseudoColorProtectionElement === null) {
220
+ this.pseudoColorProtectionElement = document.createElement('div');
221
+ this.pseudoColorProtectionElement.classList.add('media-property-renderer');
222
+ this.pseudoColorProtectionElement.classList.add('media-property-renderer-hidden');
223
+ (this.contentElement.parentNode as HTMLElement)
224
+ .insertBefore(this.pseudoColorProtectionElement, this.contentElement);
225
+ }
226
+ }
227
+
228
+ changeNestedContents(value: object): void {
229
+ if (value === null || Object.keys(value).length === 0) {
230
+ this.unsetNestedContents();
231
+ } else {
232
+ if (this.pseudoColorProtectionElement !== null) {
233
+ this.pseudoColorProtectionElement.remove();
234
+ this.pseudoColorProtectionElement = null;
235
+ }
236
+ this.contentElement.classList.remove('media-property-renderer-hidden');
237
+ this.contents.removeChildren();
238
+ const jsonWrapperElement =
239
+ new SourceFrame.JSONView.JSONView(new SourceFrame.JSONView.ParsedJSON(value, '', ''), true);
240
+ jsonWrapperElement.show(this.contents);
241
+ }
242
+ }
243
+
216
244
  changeContents(value: string|null): void {
217
245
  if (value === null) {
218
- this.contentElement.classList.add('media-property-renderer-hidden');
219
- if (this.pseudoColorProtectionElement === null) {
220
- this.pseudoColorProtectionElement = document.createElement('div');
221
- this.pseudoColorProtectionElement.classList.add('media-property-renderer');
222
- this.pseudoColorProtectionElement.classList.add('media-property-renderer-hidden');
223
- (this.contentElement.parentNode as HTMLElement)
224
- .insertBefore(this.pseudoColorProtectionElement, this.contentElement);
225
- }
246
+ this.unsetNestedContents();
226
247
  } else {
227
248
  if (this.pseudoColorProtectionElement !== null) {
228
249
  this.pseudoColorProtectionElement.remove();
@@ -260,6 +281,13 @@ export class DefaultPropertyRenderer extends PropertyRenderer {
260
281
  }
261
282
  }
262
283
 
284
+ export class NestedPropertyRenderer extends PropertyRenderer {
285
+ constructor(title: Platform.UIString.LocalizedString, content: object) {
286
+ super(title);
287
+ this.changeNestedContents(content);
288
+ }
289
+ }
290
+
263
291
  export class DimensionPropertyRenderer extends PropertyRenderer {
264
292
  private width: number;
265
293
  private height: number;
@@ -337,7 +365,11 @@ export class TrackManager {
337
365
  addNewTab(tabs: GenericTrackMenu|NoTracksPlaceholderMenu, tabData: TabData, tabNumber: number): void {
338
366
  const tabElements = [];
339
367
  for (const [name, data] of Object.entries(tabData)) {
340
- tabElements.push(new DefaultPropertyRenderer(i18n.i18n.lockedString(name), data));
368
+ if (typeof data === 'object') {
369
+ tabElements.push(new NestedPropertyRenderer(i18n.i18n.lockedString(name), data));
370
+ } else {
371
+ tabElements.push(new DefaultPropertyRenderer(i18n.i18n.lockedString(name), data));
372
+ }
341
373
  }
342
374
  const newTab = new AttributesView(tabElements);
343
375
 
@@ -12,6 +12,8 @@
12
12
  line-height: 20px;
13
13
  min-height: 28px;
14
14
  padding: 4px 10px;
15
+ display: block;
16
+ overflow: hidden;
15
17
  }
16
18
 
17
19
  .media-property-renderer-hidden {
@@ -19,14 +21,23 @@
19
21
  }
20
22
 
21
23
  .media-property-renderer-title {
22
- font-size: 14px;
24
+ font-size: 12px;
25
+ float: left;
26
+ width: 150px;
27
+ }
28
+
29
+ .media-property-renderer-title::first-letter {
30
+ text-transform: uppercase;
23
31
  }
24
32
 
25
33
  .media-property-renderer-contents {
26
- position: absolute;
34
+ position: relative;
27
35
  left: 200px;
36
+ }
37
+
38
+ .media-property-renderer-contents > .json-view {
28
39
  overflow: hidden;
29
- height: 20px;
40
+ padding: 0;
30
41
  }
31
42
 
32
43
  .media-property-renderer:nth-child(even) {
@@ -37,10 +48,18 @@
37
48
  background: var(--color-background-hover-overlay);
38
49
  }
39
50
 
51
+ .media-property-renderer:has(.json-view) {
52
+ padding-bottom: 0;
53
+ }
54
+
40
55
  .-theme-with-dark-background .media-property-renderer:nth-child(even) {
41
56
  background: rgb(41 41 41);
42
57
  }
43
58
 
59
+ .media-property-renderer:has(.json-view > .expanded) {
60
+ padding-bottom: 4px;
61
+ }
62
+
44
63
  .media-properties-frame {
45
64
  display: block;
46
65
  overflow-x: hidden;
@@ -1662,7 +1662,7 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
1662
1662
  }
1663
1663
 
1664
1664
  private async copyCurlCommand(request: SDK.NetworkRequest.NetworkRequest, platform: string): Promise<void> {
1665
- const command = await this.generateCurlCommand(request, platform);
1665
+ const command = await NetworkLogView.generateCurlCommand(request, platform);
1666
1666
  Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(command);
1667
1667
  }
1668
1668
 
@@ -2092,7 +2092,7 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
2092
2092
  return commands.join(' ;\n');
2093
2093
  }
2094
2094
 
2095
- private async generateCurlCommand(request: SDK.NetworkRequest.NetworkRequest, platform: string): Promise<string> {
2095
+ static async generateCurlCommand(request: SDK.NetworkRequest.NetworkRequest, platform: string): Promise<string> {
2096
2096
  let command: string[] = [];
2097
2097
  // Most of these headers are derived from the URL and are automatically added by cURL.
2098
2098
  // The |Accept-Encoding| header is ignored to prevent decompression errors. crbug.com/1015321
@@ -2115,7 +2115,7 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
2115
2115
  gets to MS Crt parser safely.
2116
2116
 
2117
2117
  The % character is special because MS Crt parser will try and look for
2118
- ENV variables and fill them in it's place. We cannot escape them with %
2118
+ ENV variables and fill them in its place. We cannot escape them with %
2119
2119
  and cannot escape them with ^ (because it's cmd.exe's escape not MS Crt
2120
2120
  parser); So we can get cmd.exe parser to escape the character after it,
2121
2121
  if it is followed by a valid beginning character of an ENV variable.
@@ -2191,7 +2191,14 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
2191
2191
  if (ignoredHeaders.has(name.toLowerCase())) {
2192
2192
  continue;
2193
2193
  }
2194
- command.push('-H ' + escapeString(name + ': ' + header.value));
2194
+ if (header.value.trim()) {
2195
+ command.push('-H ' + escapeString(name + ': ' + header.value));
2196
+ } else {
2197
+ // A header passed with -H with no value or only whitespace as its
2198
+ // value tells curl to not set the header at all. To post an empty
2199
+ // header, you have to terminate it with a semicolon.
2200
+ command.push('-H ' + escapeString(name + ';'));
2201
+ }
2195
2202
  }
2196
2203
  command = command.concat(data);
2197
2204
  command.push('--compressed');
@@ -2205,7 +2212,8 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
2205
2212
  private async generateAllCurlCommand(requests: SDK.NetworkRequest.NetworkRequest[], platform: string):
2206
2213
  Promise<string> {
2207
2214
  const nonBlobRequests = this.filterOutBlobRequests(requests);
2208
- const commands = await Promise.all(nonBlobRequests.map(request => this.generateCurlCommand(request, platform)));
2215
+ const commands =
2216
+ await Promise.all(nonBlobRequests.map(request => NetworkLogView.generateCurlCommand(request, platform)));
2209
2217
  if (platform === 'win') {
2210
2218
  return commands.join(' &\r\n');
2211
2219
  }
@@ -594,12 +594,17 @@ export class NavigatorView extends UI.Widget.VBox implements SDK.TargetManager.O
594
594
  project: Workspace.Workspace.Project, target: SDK.Target.Target|null,
595
595
  frame: SDK.ResourceTreeModel.ResourceTreeFrame|null, projectOrigin: string, isFromSourceMap: boolean,
596
596
  path: Platform.DevToolsPath.EncodedPathString): string {
597
- let targetId = target && !(this.groupByAuthored && isFromSourceMap) ? target.id() : '';
598
597
  const projectId = project.type() === Workspace.Workspace.projectTypes.FileSystem ? project.id() : '';
598
+ let targetId = target && !(this.groupByAuthored && isFromSourceMap) ? target.id() : '';
599
+ let frameId = this.groupByFrame && frame ? frame.id : '';
599
600
  if (this.groupByAuthored) {
600
- targetId = isFromSourceMap ? 'Authored' : 'Deployed:' + targetId;
601
+ if (isFromSourceMap) {
602
+ targetId = 'Authored';
603
+ frameId = '';
604
+ } else {
605
+ targetId = 'Deployed:' + targetId;
606
+ }
601
607
  }
602
- const frameId = this.groupByFrame && frame ? frame.id : '';
603
608
  return targetId + ':' + projectId + ':' + frameId + ':' + projectOrigin + ':' + path;
604
609
  }
605
610
 
@@ -119,19 +119,14 @@ export class BreakpointsView extends HTMLElement {
119
119
  }
120
120
 
121
121
  #renderBreakpointGroup(group: BreakpointGroup): LitHtml.TemplateResult {
122
- const groupClassMap = {
123
- 'expanded': group.expanded,
124
- };
125
122
  // clang-format off
126
123
  return LitHtml.html`
127
- <div data-group='true' class=${LitHtml.Directives.classMap(groupClassMap)}>
128
- <div class='group-header' @click=${(): void => this.#onGroupExpandToggled(group)}>
129
- <span class='triangle'></span>
130
- ${this.#renderFileIcon()}
131
- <span class='group-header-title'>${group.name}</span>
132
- </div>
133
- ${group.expanded? LitHtml.html`
134
- ${group.breakpointItems.map(entry => this.#renderBreakpointEntry(entry))}` : LitHtml.nothing}
124
+ <details data-group='true' ?open=${group.expanded} @click=${(e: Event): void => this.#onGroupExpandToggled(e, group)}>
125
+ <summary>
126
+ <span class='group-header'>${this.#renderFileIcon()}<span class='group-header-title'>${group.name}</span></span>
127
+ </summary>
128
+ ${LitHtml.html`
129
+ ${group.breakpointItems.map(entry => this.#renderBreakpointEntry(entry))}`}
135
130
  </div>
136
131
  `;
137
132
  // clang-format on
@@ -155,7 +150,7 @@ export class BreakpointsView extends HTMLElement {
155
150
 
156
151
  // clang-format off
157
152
  return LitHtml.html`
158
- <div class=${LitHtml.Directives.classMap(classMap)} aria-label=${breakpointItemDescription} tabIndex=${breakpointItem.isHit ? 0 : 1}>
153
+ <div class=${LitHtml.Directives.classMap(classMap)} aria-label=${breakpointItemDescription} tabIndex=${breakpointItem.isHit ? 0 : -1}>
159
154
  <label class='checkbox-label'>
160
155
  <input type='checkbox' aria-label=${breakpointItem.location} ?indeterminate=${breakpointItem.status === BreakpointStatus.INDETERMINATE} ?checked=${breakpointItem.status === BreakpointStatus.ENABLED} @change=${(e: Event): void => this.#onCheckboxToggled(e, breakpointItem)}>
161
156
  </label>
@@ -185,10 +180,10 @@ export class BreakpointsView extends HTMLElement {
185
180
  return i18nString(UIStrings.breakpointHit, {PH1: checkboxDescription});
186
181
  }
187
182
 
188
- #onGroupExpandToggled(group: BreakpointGroup): void {
189
- group.expanded = !group.expanded;
190
- this.dispatchEvent(new ExpandedStateChangedEvent(group.url, group.expanded));
191
- void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
183
+ #onGroupExpandToggled(e: Event, group: BreakpointGroup): void {
184
+ const detailsElement = e.target as HTMLDetailsElement;
185
+ group.expanded = detailsElement.open;
186
+ this.dispatchEvent(new ExpandedStateChangedEvent(group.url, detailsElement.open));
192
187
  }
193
188
 
194
189
  #onCheckboxToggled(e: Event, item: BreakpointItem): void {
@@ -9,6 +9,7 @@ import * as Buttons from '../../../ui/components/buttons/buttons.js';
9
9
  import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
10
10
  import * as UI from '../../../ui/legacy/legacy.js';
11
11
  import * as LitHtml from '../../../ui/lit-html/lit-html.js';
12
+ import type * as Protocol from '../../../generated/protocol.js';
12
13
 
13
14
  import HeadersViewStyles from './HeadersView.css.js';
14
15
 
@@ -81,23 +82,8 @@ export class HeadersView extends UI.View.SimpleView {
81
82
  parsingError = true;
82
83
  }
83
84
 
84
- // Header overrides are stored as the key-value pairs of a JSON object on
85
- // disk. For the editor we want them as an array instead, so that we can
86
- // access/add/remove entries by their index.
87
- const arrayOfHeaderOverrideArrays: HeaderOverride[] = headerOverrides.map(headerOverride => {
88
- return {
89
- applyTo: headerOverride.applyTo,
90
- headers: Object.entries(headerOverride.headers).map(([headerName, headerValue]) => {
91
- return {
92
- name: headerName,
93
- value: headerValue,
94
- };
95
- }),
96
- };
97
- });
98
-
99
85
  this.#headersViewComponent.data = {
100
- headerOverrides: arrayOfHeaderOverrideArrays,
86
+ headerOverrides,
101
87
  uiSourceCode: this.#uiSourceCode,
102
88
  parsingError,
103
89
  };
@@ -128,18 +114,8 @@ export class HeadersView extends UI.View.SimpleView {
128
114
  }
129
115
  }
130
116
 
131
- type Header = {
132
- name: string,
133
- value: string,
134
- };
135
-
136
- type HeaderOverride = {
137
- applyTo: string,
138
- headers: Header[],
139
- };
140
-
141
117
  export interface HeadersViewComponentData {
142
- headerOverrides: HeaderOverride[];
118
+ headerOverrides: Persistence.NetworkPersistenceManager.HeaderOverride[];
143
119
  uiSourceCode: Workspace.UISourceCode.UISourceCode;
144
120
  parsingError: boolean;
145
121
  }
@@ -148,7 +124,7 @@ export class HeadersViewComponent extends HTMLElement {
148
124
  static readonly litTagName = LitHtml.literal`devtools-sources-headers-view`;
149
125
  readonly #shadow = this.attachShadow({mode: 'open'});
150
126
  readonly #boundRender = this.#render.bind(this);
151
- #headerOverrides: HeaderOverride[] = [];
127
+ #headerOverrides: Persistence.NetworkPersistenceManager.HeaderOverride[] = [];
152
128
  #uiSourceCode: Workspace.UISourceCode.UISourceCode|null = null;
153
129
  #parsingError = false;
154
130
  #focusElement: {blockIndex: number, headerIndex?: number}|null = null;
@@ -213,7 +189,7 @@ export class HeadersViewComponent extends HTMLElement {
213
189
  selection?.removeAllRanges();
214
190
  }
215
191
 
216
- #generateNextHeaderName(headers: Header[]): string {
192
+ #generateNextHeaderName(headers: Protocol.Fetch.HeaderEntry[]): string {
217
193
  const takenNames = new Set<string>(headers.map(header => header.name));
218
194
  let idx = 1;
219
195
  while (takenNames.has('headerName' + idx)) {
@@ -270,17 +246,7 @@ export class HeadersViewComponent extends HTMLElement {
270
246
  }
271
247
 
272
248
  #onHeadersChanged(): void {
273
- // In the editor header overrides are represented by items in an array, so
274
- // that we can access/add/remove entries by their index. On disk, they are
275
- // stored as key-value pairs of a JSON object instead.
276
- const arrayOfHeaderOverrideObjects: Persistence.NetworkPersistenceManager.HeaderOverride[] =
277
- this.#headerOverrides.map(headerOverride => {
278
- return {
279
- applyTo: headerOverride.applyTo,
280
- headers: headerOverride.headers.reduce((a, v) => ({...a, [v.name]: v.value}), {}),
281
- };
282
- });
283
- this.#uiSourceCode?.setWorkingCopy(JSON.stringify(arrayOfHeaderOverrideObjects, null, 2));
249
+ this.#uiSourceCode?.setWorkingCopy(JSON.stringify(this.#headerOverrides, null, 2));
284
250
  }
285
251
 
286
252
  #render(): void {
@@ -355,7 +321,8 @@ export class HeadersViewComponent extends HTMLElement {
355
321
  // clang-format on
356
322
  }
357
323
 
358
- #renderHeaderRow(header: Header, blockIndex: number, headerIndex: number): LitHtml.TemplateResult {
324
+ #renderHeaderRow(header: Protocol.Fetch.HeaderEntry, blockIndex: number, headerIndex: number):
325
+ LitHtml.TemplateResult {
359
326
  // clang-format off
360
327
  return LitHtml.html`
361
328
  <div class="row padded" data-block-index=${blockIndex} data-header-index=${headerIndex}>
@@ -35,26 +35,34 @@ input {
35
35
  margin: 3px 0;
36
36
  }
37
37
 
38
- .triangle {
39
- margin-left: 6px;
40
- margin-right: 4px;
41
- display: inline-block;
38
+ details > summary {
39
+ height: 18px;
40
+ list-style: none;
41
+ display: flex;
42
+ padding: 4px 4px 4px 6px;
43
+ align-items: center;
44
+ }
45
+
46
+ details > summary::before {
47
+ display: block;
48
+ user-select: none;
42
49
  -webkit-mask-image: var(--image-file-treeoutlineTriangles);
43
50
  -webkit-mask-size: 32px 24px;
44
51
  -webkit-mask-position: 0 0;
45
- background-color: var(--color-text-primary);
52
+ background-color: var(--color-text-secondary);
53
+ content: "";
46
54
  height: 12px;
47
- width: 8px;
55
+ width: 9px;
56
+ overflow: hidden;
48
57
  }
49
58
 
50
- .group-header {
51
- display: flex;
52
- align-items: center;
53
- height: 18px;
59
+ details[open] > summary::before {
60
+ -webkit-mask-position: -16px 0;
54
61
  }
55
62
 
56
- .expanded > .group-header > .triangle {
57
- -webkit-mask-position: -16px 0;
63
+ .group-header {
64
+ display: inline-flex;
65
+ align-items: center;
58
66
  }
59
67
 
60
68
  .group-header-title {
@@ -84,7 +92,7 @@ input {
84
92
  }
85
93
 
86
94
  .checkbox-label {
87
- padding-left: 24px;
95
+ padding-left: 20px;
88
96
  display: flex;
89
97
  align-items: center;
90
98
  }
@@ -136,7 +136,7 @@ export class TimelineLoader implements Common.StringOutputStream.OutputStream {
136
136
  this.firstRawChunk = false;
137
137
 
138
138
  if (this.state === State.Initial) {
139
- if (chunk.startsWith('{"nodes":[')) {
139
+ if (chunk.match(/^{(\s)*"nodes":(\s)*\[/)) {
140
140
  this.state = State.LoadingCPUProfileFormat;
141
141
  } else if (chunk[0] === '{') {
142
142
  this.state = State.LookingForEvents;
@@ -95,15 +95,17 @@ export class LinearMemoryHighlightChipList extends HTMLElement {
95
95
  <span class="value">${expressionName}</span><span class="separator">: </span><span>${expressionType}</span>
96
96
  </span>
97
97
  </button>
98
- <button class="delete-highlight-button" title=${
99
- i18nString(UIStrings.deleteHighlight)} @click=${():void => this.#onDeleteHighlightClick(highlightInfo)}>
100
- <${IconButton.Icon.Icon.litTagName} .data=${{
101
- iconName: 'close-icon',
102
- color: 'black',
103
- width: '7px',
104
- } as IconButton.Icon.IconData}>
105
- </${IconButton.Icon.Icon.litTagName}>
106
- </button>
98
+ <div class="delete-highlight-container">
99
+ <button class="delete-highlight-button" title=${
100
+ i18nString(UIStrings.deleteHighlight)} @click=${():void => this.#onDeleteHighlightClick(highlightInfo)}>
101
+ <${IconButton.Icon.Icon.litTagName} .data=${{
102
+ iconName: 'close-icon',
103
+ color: 'var(--color-text-primary)',
104
+ width: '7px',
105
+ } as IconButton.Icon.IconData}>
106
+ </${IconButton.Icon.Icon.litTagName}>
107
+ </button>
108
+ </div>
107
109
  </div>
108
110
  `;
109
111
  // clang-format off
@@ -5,81 +5,99 @@
5
5
  */
6
6
 
7
7
  .highlight-chip-list {
8
- min-height: 24px;
8
+ min-height: 20px;
9
9
  display: flex;
10
10
  flex-wrap: wrap;
11
11
  justify-content: left;
12
12
  align-items: center;
13
13
  background-color: var(--color-background);
14
- color: var(--color-text-primary);
14
+ margin: 8px 0;
15
+ gap: 8px;
16
+ row-gap: 6px;
15
17
  }
16
18
 
17
19
  .highlight-chip {
18
- background: transparent;
19
- color: var(--color-text-primary);
20
- border: 1px solid var(--color-background-elevation-2);
21
- height: 15px;
22
- margin-right: 5px;
23
- padding: 1px;
20
+ background: var(--color-background);
21
+ border: 1px solid var(--color-button-secondary-border);
22
+ height: 18px;
24
23
  border-radius: 4px;
25
- margin-bottom: 1px;
26
- display: flex;
24
+ flex: 0 0 auto;
25
+ max-width: 250px;
26
+ position: relative;
27
+ padding: 0 6px;
27
28
  }
28
29
 
29
30
  .highlight-chip:hover {
30
31
  background-color: var(--color-background-elevation-1);
31
32
  }
32
33
 
34
+ .delete-highlight-container {
35
+ display: none;
36
+ height: 100%;
37
+ position: absolute;
38
+ right: 0;
39
+ top: 0;
40
+ border-radius: 4px;
41
+ width: 24px;
42
+ align-items: center;
43
+ justify-content: center;
44
+ }
45
+
33
46
  .delete-highlight-button {
34
- width: 15px;
35
- height: 15px;
36
- border: none;
37
- padding: 0;
38
47
  cursor: pointer;
39
- background: none;
40
- border-radius: 50%;
48
+ width: 13px;
49
+ height: 13px;
50
+ border: none;
51
+ background-color: transparent;
41
52
  display: flex;
42
- justify-content: center;
43
53
  align-items: center;
44
- margin: 0 2px;
54
+ justify-content: center;
45
55
  }
46
56
 
47
57
  .delete-highlight-button:hover {
48
58
  background-color: var(--color-details-hairline);
59
+ border-radius: 50%;
49
60
  }
50
61
 
51
- .highlight-chip > .delete-highlight-button {
52
- visibility: hidden;
62
+ .highlight-chip:hover > .delete-highlight-container {
63
+ display: flex;
64
+ /* To avoid issues with stacking semi-transparent colors, we use a hardcoded solid color here. */
65
+ background: linear-gradient(90deg, transparent 0%, rgb(241 243 244) 25%); /* stylelint-disable-line plugin/use_theme_colors */
53
66
  }
54
67
 
55
- .highlight-chip:hover > .delete-highlight-button {
56
- visibility: visible;
68
+ :host-context(.-theme-with-dark-background) .highlight-chip:hover > .delete-highlight-container {
69
+ display: flex;
70
+ /* To avoid issues with stacking semi-transparent colors, we use a hardcoded solid color here. */
71
+ background: linear-gradient(90deg, transparent 0%, rgb(41 42 45) 25%); /* stylelint-disable-line plugin/use_theme_colors */
57
72
  }
58
73
 
59
74
  .jump-to-highlight-button {
60
75
  cursor: pointer;
61
- padding: 0 0 0 4px;
76
+ padding: 0;
62
77
  border: none;
63
78
  background: none;
64
- display: flex;
79
+ height: 100%;
65
80
  align-items: center;
66
- }
67
-
68
- .jump-to-highlight-button:hover {
69
- color: var(--color-text-primary);
81
+ max-width: 100%;
82
+ overflow: hidden;
70
83
  }
71
84
 
72
85
  .delete-highlight-button devtools-icon {
73
- --icon-color: var(--color-text-primary);
86
+ width: 13px;
87
+ height: 13px;
88
+ display: flex;
89
+ align-items: center;
90
+ justify-content: center;
91
+ border-radius: 50%;
74
92
  }
75
93
 
76
94
  .source-code {
77
95
  font-family: var(--source-code-font-family);
78
96
  font-size: var(--source-code-font-size);
79
- max-width: 250px;
80
97
  overflow: hidden;
81
98
  text-overflow: ellipsis;
82
99
  white-space: nowrap;
100
+ color: var(--color-text-primary);
83
101
  }
84
102
 
85
103
  .value {
@@ -68,7 +68,7 @@ export class TextPrompt extends HTMLElement {
68
68
 
69
69
  onInput(): void {
70
70
  this.#suggestion().value = this.#text();
71
- this.dispatchEvent(new PromptInputEvent(this.#text().trim()));
71
+ this.dispatchEvent(new PromptInputEvent(this.#text()));
72
72
  }
73
73
 
74
74
  onKeyDown(event: KeyboardEvent): void {
@@ -209,7 +209,7 @@ export class FilteredListWidget extends Common.ObjectWrapper.eventMixin<EventTyp
209
209
  }
210
210
 
211
211
  private cleanValue(): string {
212
- return this.query.substring(this.prefix.length);
212
+ return this.query.substring(this.prefix.length).trim();
213
213
  }
214
214
 
215
215
  wasShown(): void {
package/package.json CHANGED
@@ -56,5 +56,5 @@
56
56
  "unittest": "scripts/test/run_unittests.py --no-text-coverage",
57
57
  "watch": "vpython third_party/node/node.py --output scripts/watch_build.js"
58
58
  },
59
- "version": "1.0.1035963"
59
+ "version": "1.0.1037221"
60
60
  }