chrome-devtools-frontend 1.0.977567 → 1.0.978200

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.
@@ -380,7 +380,29 @@ export enum PanelCodes {
380
380
  'settings-keybinds' = 38,
381
381
  'cssoverview' = 39,
382
382
  'chrome_recorder' = 40,
383
- MaxValue = 41,
383
+ 'trust_tokens' = 41,
384
+ 'reporting_api' = 42,
385
+ 'interest_groups' = 43,
386
+ 'back_forward_cache' = 44,
387
+ 'service_worker_cache' = 45,
388
+ 'background_service_backgroundFetch' = 46,
389
+ 'background_service_backgroundSync' = 47,
390
+ 'background_service_pushMessaging' = 48,
391
+ 'background_service_notifications' = 49,
392
+ 'background_service_paymentHandler' = 50,
393
+ 'background_service_periodicBackgroundSync' = 51,
394
+ 'service_workers' = 52,
395
+ 'app_manifest' = 53,
396
+ 'storage' = 54,
397
+ 'cookies' = 55,
398
+ 'frame_details' = 56,
399
+ 'frame_resource' = 57,
400
+ 'frame_window' = 58,
401
+ 'frame_worker' = 59,
402
+ 'dom_storage' = 60,
403
+ 'indexed_db' = 61,
404
+ 'web_sql' = 62,
405
+ MaxValue = 63,
384
406
  }
385
407
  /* eslint-enable @typescript-eslint/naming-convention */
386
408
 
@@ -372,7 +372,7 @@ export class NetworkPersistenceManager extends Common.ObjectWrapper.ObjectWrappe
372
372
  async generateHeaderPatterns(uiSourceCode: Workspace.UISourceCode.UISourceCode):
373
373
  Promise<{headerPatterns: Set<string>, path: string, overridesWithRegex: HeaderOverrideWithRegex[]}> {
374
374
  const headerPatterns = new Set<string>();
375
- const content = (await uiSourceCode.requestContent()).content || '';
375
+ const content = (await uiSourceCode.requestContent()).content || '[]';
376
376
  let headerOverrides: HeaderOverride[] = [];
377
377
  try {
378
378
  headerOverrides = JSON.parse(content) as HeaderOverride[];
@@ -657,7 +657,7 @@ export type EventTypes = {
657
657
  [Events.ProjectChanged]: Workspace.Workspace.Project|null,
658
658
  };
659
659
 
660
- interface HeaderOverride {
660
+ export interface HeaderOverride {
661
661
  applyTo: string;
662
662
  headers: Protocol.Network.Headers;
663
663
  }
@@ -668,7 +668,7 @@ interface HeaderOverrideWithRegex {
668
668
  }
669
669
 
670
670
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
671
- function isHeaderOverride(arg: any): arg is HeaderOverride {
671
+ export function isHeaderOverride(arg: any): arg is HeaderOverride {
672
672
  if (!(arg && arg.applyTo && typeof (arg.applyTo === 'string') && arg.headers && Object.keys(arg.headers).length)) {
673
673
  return false;
674
674
  }
@@ -7,6 +7,7 @@ import * as i18n from '../../core/i18n/i18n.js';
7
7
  import * as SDK from '../../core/sdk/sdk.js';
8
8
  import * as UI from '../../ui/legacy/legacy.js';
9
9
  import * as ApplicationComponents from './components/components.js';
10
+ import * as Host from '../../core/host/host.js';
10
11
 
11
12
  import {ApplicationPanelTreeElement, ExpandableApplicationPanelTreeElement} from './ApplicationPanelTreeElement.js';
12
13
  import type {ResourcesPanel} from './ResourcesPanel.js';
@@ -164,6 +165,7 @@ export class SWCacheTreeElement extends ApplicationPanelTreeElement {
164
165
  }
165
166
 
166
167
  this.showView(this.view);
168
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.service_worker_cache]);
167
169
  return false;
168
170
  }
169
171
 
@@ -192,6 +194,7 @@ export class BackForwardCacheTreeElement extends ApplicationPanelTreeElement {
192
194
  this.view = new ApplicationComponents.BackForwardCacheView.BackForwardCacheViewWrapper();
193
195
  }
194
196
  this.showView(this.view);
197
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.back_forward_cache]);
195
198
  return false;
196
199
  }
197
200
  }
@@ -831,6 +831,7 @@ export class BackgroundServiceTreeElement extends ApplicationPanelTreeElement {
831
831
  }
832
832
  this.showView(this.view);
833
833
  UI.Context.Context.instance().setFlavor(BackgroundServiceView, this.view);
834
+ Host.userMetrics.panelShown('background_service_' + this.serviceName);
834
835
  return false;
835
836
  }
836
837
  }
@@ -854,6 +855,7 @@ export class DatabaseTreeElement extends ApplicationPanelTreeElement {
854
855
  onselect(selectedByUser?: boolean): boolean {
855
856
  super.onselect(selectedByUser);
856
857
  this.sidebar.showDatabase(this.database);
858
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.web_sql]);
857
859
  return false;
858
860
  }
859
861
 
@@ -891,6 +893,7 @@ export class DatabaseTableTreeElement extends ApplicationPanelTreeElement {
891
893
  onselect(selectedByUser?: boolean): boolean {
892
894
  super.onselect(selectedByUser);
893
895
  this.sidebar.showDatabase(this.database, this.tableName);
896
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.web_sql]);
894
897
  return false;
895
898
  }
896
899
  }
@@ -914,6 +917,7 @@ export class ServiceWorkersTreeElement extends ApplicationPanelTreeElement {
914
917
  this.view = new ServiceWorkersView();
915
918
  }
916
919
  this.showView(this.view);
920
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.service_workers]);
917
921
  return false;
918
922
  }
919
923
  }
@@ -936,6 +940,7 @@ export class AppManifestTreeElement extends ApplicationPanelTreeElement {
936
940
  this.view = new AppManifestView();
937
941
  }
938
942
  this.showView(this.view);
943
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.app_manifest]);
939
944
  return false;
940
945
  }
941
946
  }
@@ -958,6 +963,7 @@ export class ClearStorageTreeElement extends ApplicationPanelTreeElement {
958
963
  this.view = new StorageView();
959
964
  }
960
965
  this.showView(this.view);
966
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.storage]);
961
967
  return false;
962
968
  }
963
969
  }
@@ -1170,6 +1176,7 @@ export class IDBDatabaseTreeElement extends ApplicationPanelTreeElement {
1170
1176
  }
1171
1177
 
1172
1178
  this.showView(this.view);
1179
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.indexed_db]);
1173
1180
  return false;
1174
1181
  }
1175
1182
 
@@ -1302,6 +1309,7 @@ export class IDBObjectStoreTreeElement extends ApplicationPanelTreeElement {
1302
1309
  }
1303
1310
 
1304
1311
  this.showView(this.view);
1312
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.indexed_db]);
1305
1313
  return false;
1306
1314
  }
1307
1315
 
@@ -1391,6 +1399,7 @@ export class IDBIndexTreeElement extends ApplicationPanelTreeElement {
1391
1399
  }
1392
1400
 
1393
1401
  this.showView(this.view);
1402
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.indexed_db]);
1394
1403
  return false;
1395
1404
  }
1396
1405
 
@@ -1417,6 +1426,7 @@ export class DOMStorageTreeElement extends ApplicationPanelTreeElement {
1417
1426
 
1418
1427
  onselect(selectedByUser?: boolean): boolean {
1419
1428
  super.onselect(selectedByUser);
1429
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.dom_storage]);
1420
1430
  this.resourcesPanel.showDOMStorage(this.domStorage);
1421
1431
  return false;
1422
1432
  }
@@ -1469,6 +1479,7 @@ export class CookieTreeElement extends ApplicationPanelTreeElement {
1469
1479
  onselect(selectedByUser?: boolean): boolean {
1470
1480
  super.onselect(selectedByUser);
1471
1481
  this.resourcesPanel.showCookies(this.target, this.cookieDomainInternal);
1482
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.cookies]);
1472
1483
  return false;
1473
1484
  }
1474
1485
  }
@@ -1810,6 +1821,7 @@ export class FrameTreeElement extends ApplicationPanelTreeElement {
1810
1821
  } else {
1811
1822
  this.view.update();
1812
1823
  }
1824
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.frame_details]);
1813
1825
  this.showView(this.view);
1814
1826
 
1815
1827
  this.listItemElement.classList.remove('hovered');
@@ -1978,6 +1990,7 @@ export class FrameResourceTreeElement extends ApplicationPanelTreeElement {
1978
1990
  } else {
1979
1991
  void this.panel.scheduleShowView(this.preparePreview());
1980
1992
  }
1993
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.frame_resource]);
1981
1994
  return false;
1982
1995
  }
1983
1996
 
@@ -2065,6 +2078,7 @@ class FrameWindowTreeElement extends ApplicationPanelTreeElement {
2065
2078
  this.view.update();
2066
2079
  }
2067
2080
  this.showView(this.view);
2081
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.frame_window]);
2068
2082
  return false;
2069
2083
  }
2070
2084
 
@@ -2093,6 +2107,7 @@ class WorkerTreeElement extends ApplicationPanelTreeElement {
2093
2107
  this.view.update();
2094
2108
  }
2095
2109
  this.showView(this.view);
2110
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.frame_worker]);
2096
2111
  return false;
2097
2112
  }
2098
2113
 
@@ -2,6 +2,7 @@
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 Host from '../../core/host/host.js';
5
6
  import * as i18n from '../../core/i18n/i18n.js';
6
7
  import * as SDK from '../../core/sdk/sdk.js';
7
8
  import * as UI from '../../ui/legacy/legacy.js';
@@ -51,6 +52,7 @@ export class InterestGroupTreeElement extends ApplicationPanelTreeElement {
51
52
  onselect(selectedByUser?: boolean): boolean {
52
53
  super.onselect(selectedByUser);
53
54
  this.showView(this.view);
55
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.interest_groups]);
54
56
  return false;
55
57
  }
56
58
 
@@ -2,6 +2,7 @@
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 Host from '../../core/host/host.js';
5
6
  import * as i18n from '../../core/i18n/i18n.js';
6
7
  import * as UI from '../../ui/legacy/legacy.js';
7
8
 
@@ -37,6 +38,7 @@ export class ReportingApiTreeElement extends ApplicationPanelTreeElement {
37
38
  this.view = new ReportingApiView();
38
39
  }
39
40
  this.showView(this.view);
41
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.reporting_api]);
40
42
  return false;
41
43
  }
42
44
  }
@@ -9,6 +9,7 @@ import * as UI from '../../ui/legacy/legacy.js';
9
9
  import {ApplicationPanelTreeElement} from './ApplicationPanelTreeElement.js';
10
10
  import * as ApplicationComponents from './components/components.js';
11
11
  import type {ResourcesPanel} from './ResourcesPanel.js';
12
+ import * as Host from '../../core/host/host.js';
12
13
 
13
14
  const UIStrings = {
14
15
  /**
@@ -41,6 +42,7 @@ export class TrustTokensTreeElement extends ApplicationPanelTreeElement {
41
42
  this.view = new TrustTokensViewWidgetWrapper();
42
43
  }
43
44
  this.showView(this.view);
45
+ Host.userMetrics.panelShown(Host.UserMetrics.PanelCodes[Host.UserMetrics.PanelCodes.trust_tokens]);
44
46
  return false;
45
47
  }
46
48
  }
@@ -8,6 +8,58 @@
8
8
  flex-grow: 1;
9
9
  }
10
10
 
11
+ .row {
12
+ display: flex;
13
+ flex-direction: row;
14
+ color: var(--color-syntax-1);
15
+ font-family: var(--monospace-font-family);
16
+ font-size: var(--monospace-font-size);
17
+ align-items: center;
18
+ line-height: 18px;
19
+ margin: 2em 0 0 0.5em;
20
+ }
21
+
22
+ .row:first-child {
23
+ margin-top: 0.5em;
24
+ }
25
+
26
+ .row devtools-button {
27
+ line-height: 1;
28
+ margin-left: 0.5em;
29
+ }
30
+
31
+ .padded {
32
+ margin: 0.25em 0 0 2em;
33
+ }
34
+
35
+ .separator {
36
+ margin-right: 0.5em;
37
+ color: var(--color-text-primary);
38
+ }
39
+
40
+ .editable {
41
+ cursor: text;
42
+ color: var(--color-text-primary);
43
+ overflow-wrap: anywhere;
44
+ min-height: 18px;
45
+ line-height: 18px;
46
+ min-width: 0.5em;
47
+ background: transparent;
48
+ border: none;
49
+ outline: none;
50
+ display: inline-block;
51
+ }
52
+
53
+ .editable.red {
54
+ color: var(--color-syntax-1);
55
+ }
56
+
57
+ .editable:hover,
58
+ .editable:focus {
59
+ box-shadow: 0 0 0 1px var(--color-details-hairline);
60
+ border-radius: 2px;
61
+ }
62
+
11
63
  .center-wrapper {
12
64
  height: 100%;
13
65
  display: flex;
@@ -4,7 +4,7 @@
4
4
 
5
5
  import * as i18n from '../../../core/i18n/i18n.js';
6
6
  import * as Persistence from '../../../models/persistence/persistence.js';
7
- import type * as Workspace from '../../../models/workspace/workspace.js';
7
+ import * as Workspace from '../../../models/workspace/workspace.js';
8
8
  import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
9
9
  import * as UI from '../../../ui/legacy/legacy.js';
10
10
  import * as LitHtml from '../../../ui/lit-html/lit-html.js';
@@ -34,9 +34,52 @@ export class HeadersView extends UI.View.SimpleView {
34
34
  constructor(uiSourceCode: Workspace.UISourceCode.UISourceCode) {
35
35
  super('HeadersView');
36
36
  this.#uiSourceCode = uiSourceCode;
37
+ this.#uiSourceCode.addEventListener(
38
+ Workspace.UISourceCode.Events.WorkingCopyChanged, this.#onWorkingCopyChanged, this);
39
+ this.#uiSourceCode.addEventListener(
40
+ Workspace.UISourceCode.Events.WorkingCopyCommitted, this.#onWorkingCopyCommitted, this);
37
41
  this.element.appendChild(this.#headersViewComponent);
42
+ void this.#setInitialData();
43
+ }
44
+
45
+ async #setInitialData(): Promise<void> {
46
+ const content = await this.#uiSourceCode.requestContent();
47
+ this.#setComponentData(content.content || '');
48
+ }
49
+
50
+ #setComponentData(content: string): void {
51
+ let parsingError = false;
52
+ let headerOverrides: Persistence.NetworkPersistenceManager.HeaderOverride[] = [];
53
+ content = content || '[]';
54
+ try {
55
+ headerOverrides = JSON.parse(content) as Persistence.NetworkPersistenceManager.HeaderOverride[];
56
+ if (!headerOverrides.every(Persistence.NetworkPersistenceManager.isHeaderOverride)) {
57
+ throw 'Type mismatch after parsing';
58
+ }
59
+ } catch (e) {
60
+ console.error('Failed to parse', this.#uiSourceCode.url(), 'for locally overriding headers.');
61
+ parsingError = true;
62
+ }
63
+
64
+ // Header overrides are stored as the key-value pairs of a JSON object on
65
+ // disk. For the editor we want them as an array instead, so that we can
66
+ // access/add/remove entries by their index.
67
+ const arrayOfHeaderOverrideArrays: HeaderOverride[] = headerOverrides.map(headerOverride => {
68
+ return {
69
+ applyTo: headerOverride.applyTo,
70
+ headers: Object.entries(headerOverride.headers).map(([headerName, headerValue]) => {
71
+ return {
72
+ name: headerName,
73
+ value: headerValue,
74
+ };
75
+ }),
76
+ };
77
+ });
78
+
38
79
  this.#headersViewComponent.data = {
80
+ headerOverrides: arrayOfHeaderOverrideArrays,
39
81
  uiSourceCode: this.#uiSourceCode,
82
+ parsingError,
40
83
  };
41
84
  }
42
85
 
@@ -44,39 +87,207 @@ export class HeadersView extends UI.View.SimpleView {
44
87
  this.#uiSourceCode.commitWorkingCopy();
45
88
  Persistence.NetworkPersistenceManager.NetworkPersistenceManager.instance().updateInterceptionPatterns();
46
89
  }
90
+
91
+ #onWorkingCopyChanged(): void {
92
+ this.#setComponentData(this.#uiSourceCode.workingCopy());
93
+ }
94
+
95
+ #onWorkingCopyCommitted(): void {
96
+ this.#setComponentData(this.#uiSourceCode.workingCopy());
97
+ }
98
+
99
+ dispose(): void {
100
+ this.#uiSourceCode.removeEventListener(
101
+ Workspace.UISourceCode.Events.WorkingCopyChanged, this.#onWorkingCopyChanged, this);
102
+ this.#uiSourceCode.removeEventListener(
103
+ Workspace.UISourceCode.Events.WorkingCopyCommitted, this.#onWorkingCopyCommitted, this);
104
+ }
47
105
  }
48
106
 
107
+ type Header = {
108
+ name: string,
109
+ value: string,
110
+ };
111
+
112
+ type HeaderOverride = {
113
+ applyTo: string,
114
+ headers: Header[],
115
+ };
116
+
49
117
  export interface HeadersViewComponentData {
118
+ headerOverrides: HeaderOverride[];
50
119
  uiSourceCode: Workspace.UISourceCode.UISourceCode;
120
+ parsingError: boolean;
51
121
  }
52
122
 
53
123
  export class HeadersViewComponent extends HTMLElement {
54
124
  static readonly litTagName = LitHtml.literal`devtools-sources-headers-view`;
55
125
  readonly #shadow = this.attachShadow({mode: 'open'});
126
+ readonly #boundRender = this.#render.bind(this);
127
+ #headerOverrides: HeaderOverride[] = [];
56
128
  #uiSourceCode: Workspace.UISourceCode.UISourceCode|null = null;
129
+ #parsingError = false;
130
+
131
+ constructor() {
132
+ super();
133
+ this.#shadow.addEventListener('focusin', this.#onFocusIn.bind(this));
134
+ this.#shadow.addEventListener('focusout', this.#onFocusOut.bind(this));
135
+ this.#shadow.addEventListener('input', this.#onInput.bind(this));
136
+ this.#shadow.addEventListener('keydown', this.#onKeyDown.bind(this));
137
+ }
57
138
 
58
139
  connectedCallback(): void {
59
140
  this.#shadow.adoptedStyleSheets = [HeadersViewStyles];
60
141
  }
61
142
 
62
143
  set data(data: HeadersViewComponentData) {
144
+ this.#headerOverrides = data.headerOverrides;
63
145
  this.#uiSourceCode = data.uiSourceCode;
64
- this.#render();
146
+ this.#parsingError = data.parsingError;
147
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
148
+ }
149
+
150
+ // 'Enter' key should not create a new line in the contenteditable. Focus
151
+ // on the next contenteditable instead.
152
+ #onKeyDown(event: Event): void {
153
+ const keyboardEvent = event as KeyboardEvent;
154
+ const target = event.target as HTMLElement;
155
+ if (target.matches('.editable') && keyboardEvent.key === 'Enter') {
156
+ event.preventDefault();
157
+ this.#focusNext(target);
158
+ }
159
+ }
160
+
161
+ #focusNext(target: HTMLElement): void {
162
+ const elements = Array.from(this.#shadow.querySelectorAll('.editable')) as HTMLElement[];
163
+ const idx = elements.indexOf(target);
164
+ if (idx !== -1 && idx + 1 < elements.length) {
165
+ elements[idx + 1].focus();
166
+ }
167
+ }
168
+
169
+ #selectAllText(target: HTMLElement): void {
170
+ const selection = window.getSelection();
171
+ const range = document.createRange();
172
+ range.selectNodeContents(target);
173
+ selection?.removeAllRanges();
174
+ selection?.addRange(range);
175
+ }
176
+
177
+ #onFocusIn(e: Event): void {
178
+ const target = e.target as HTMLElement;
179
+ if (target.matches('.editable')) {
180
+ this.#selectAllText(target);
181
+ }
182
+ }
183
+
184
+ #onFocusOut(): void {
185
+ // clear selection
186
+ const selection = window.getSelection();
187
+ selection?.removeAllRanges();
188
+ }
189
+
190
+ #onInput(e: Event): void {
191
+ const target = e.target as HTMLButtonElement;
192
+ const rowElement = target.closest('.row') as HTMLElement;
193
+ const blockIndex = Number(rowElement.dataset.blockIndex);
194
+ const headerIndex = Number(rowElement.dataset.headerIndex);
195
+ if (target.matches('.header-name')) {
196
+ this.#headerOverrides[blockIndex].headers[headerIndex].name = target.innerText;
197
+ this.#onHeadersChanged();
198
+ }
199
+ if (target.matches('.header-value')) {
200
+ this.#headerOverrides[blockIndex].headers[headerIndex].value = target.innerText;
201
+ this.#onHeadersChanged();
202
+ }
203
+ if (target.matches('.apply-to')) {
204
+ this.#headerOverrides[blockIndex].applyTo = target.innerText;
205
+ this.#onHeadersChanged();
206
+ }
207
+ }
208
+
209
+ #onHeadersChanged(): void {
210
+ // In the editor header overrides are represented by items in an array, so
211
+ // that we can access/add/remove entries by their index. On disk, they are
212
+ // stored as key-value pairs of a JSON object instead.
213
+ const arrayOfHeaderOverrideObjects: Persistence.NetworkPersistenceManager.HeaderOverride[] =
214
+ this.#headerOverrides.map(headerOverride => {
215
+ return {
216
+ applyTo: headerOverride.applyTo,
217
+ headers: headerOverride.headers.reduce((a, v) => ({...a, [v.name]: v.value}), {}),
218
+ };
219
+ });
220
+ this.#uiSourceCode?.setWorkingCopy(JSON.stringify(arrayOfHeaderOverrideObjects, null, 2));
65
221
  }
66
222
 
67
223
  #render(): void {
68
- const fileName = this.#uiSourceCode?.name() || '.headers';
224
+ if (!ComponentHelpers.ScheduledRender.isScheduledRender(this)) {
225
+ throw new Error('HeadersView render was not scheduled');
226
+ }
227
+
228
+ if (this.#parsingError) {
229
+ const fileName = this.#uiSourceCode?.name() || '.headers';
230
+ // clang-format off
231
+ LitHtml.render(LitHtml.html`
232
+ <div class="center-wrapper">
233
+ <div class="centered">
234
+ <div class="error-header">${i18nString(UIStrings.errorWhenParsing, {PH1: fileName})}</div>
235
+ <div class="error-body">${i18nString(UIStrings.parsingErrorExplainer, {PH1: fileName})}</div>
236
+ </div>
237
+ </div>
238
+ `, this.#shadow, {host: this});
239
+ // clang-format on
240
+ return;
241
+ }
242
+
69
243
  // clang-format off
70
244
  LitHtml.render(LitHtml.html`
71
- <div class="center-wrapper">
72
- <div class="centered">
73
- <div class="error-header">${i18nString(UIStrings.errorWhenParsing, {PH1: fileName})}</div>
74
- <div class="error-body">${i18nString(UIStrings.parsingErrorExplainer, {PH1: fileName})}</div>
75
- </div>
76
- </div>
245
+ ${this.#headerOverrides.map((headerOverride, blockIndex) =>
246
+ LitHtml.html`
247
+ ${this.#renderApplyToRow(headerOverride.applyTo, blockIndex)}
248
+ ${headerOverride.headers.map((header, headerIndex) =>
249
+ LitHtml.html`
250
+ ${this.#renderHeaderRow(header, blockIndex, headerIndex)}
251
+ `,
252
+ )}
253
+ `,
254
+ )}
77
255
  `, this.#shadow, {host: this});
78
256
  // clang-format on
79
257
  }
258
+
259
+ #renderApplyToRow(pattern: string, blockIndex: number): LitHtml.TemplateResult {
260
+ // clang-format off
261
+ return LitHtml.html`
262
+ <div class="row" data-block-index=${blockIndex}>
263
+ <div>${i18n.i18n.lockedString('Apply to')}</div>
264
+ <div class="separator">:</div>
265
+ ${this.#renderEditable(pattern, 'apply-to')}
266
+ </div>
267
+ `;
268
+ // clang-format on
269
+ }
270
+
271
+ #renderHeaderRow(header: Header, blockIndex: number, headerIndex: number): LitHtml.TemplateResult {
272
+ // clang-format off
273
+ return LitHtml.html`
274
+ <div class="row padded" data-block-index=${blockIndex} data-header-index=${headerIndex}>
275
+ ${this.#renderEditable(header.name, 'header-name red')}
276
+ <div class="separator">:</div>
277
+ ${this.#renderEditable(header.value, 'header-value')}
278
+ `;
279
+ // clang-format on
280
+ }
281
+
282
+ #renderEditable(value: string, className?: string): LitHtml.TemplateResult {
283
+ // This uses LitHtml's `live`-directive, so that when checking whether to
284
+ // update during re-render, `value` is compared against the actual live DOM
285
+ // value of the contenteditable element and not the potentially outdated
286
+ // value from the previous render.
287
+ // clang-format off
288
+ return LitHtml.html`<span contenteditable="true" class="editable ${className}" tabindex="0" .innerText=${LitHtml.Directives.live(value)}></span>`;
289
+ // clang-format on
290
+ }
80
291
  }
81
292
 
82
293
  ComponentHelpers.CustomElements.defineComponent('devtools-sources-headers-view', HeadersViewComponent);
package/package.json CHANGED
@@ -54,5 +54,5 @@
54
54
  "unittest": "scripts/test/run_unittests.py --no-text-coverage",
55
55
  "watch": "third_party/node/node.py --output scripts/watch_build.js"
56
56
  },
57
- "version": "1.0.977567"
57
+ "version": "1.0.978200"
58
58
  }