blob: fc80357e9ee973f06c7906a3b93a2f27b4a5b884 [file] [log] [blame]
Wolfgang Beyer40530b682022-05-17 13:02:011// Copyright 2022 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import * as Common from '../../../core/common/common.js';
Wolfgang Beyer7f495ee2022-07-08 13:45:136import * as Host from '../../../core/host/host.js';
Wolfgang Beyer40530b682022-05-17 13:02:017import * as i18n from '../../../core/i18n/i18n.js';
Wolfgang Beyer7f495ee2022-07-08 13:45:138import * as Platform from '../../../core/platform/platform.js';
Wolfgang Beyer185fb1f2022-08-01 13:48:259import * as Root from '../../../core/root/root.js';
Wolfgang Beyer40530b682022-05-17 13:02:0110import * as SDK from '../../../core/sdk/sdk.js';
Wolfgang Beyer185fb1f2022-08-01 13:48:2511import * as Persistence from '../../../models/persistence/persistence.js';
12import * as Workspace from '../../../models/workspace/workspace.js';
Wolfgang Beyer40426d82022-08-03 11:08:0713import * as NetworkForward from '../../../panels/network/forward/forward.js';
Wolfgang Beyerd1dd7962022-05-24 15:48:4814import * as Buttons from '../../../ui/components/buttons/buttons.js';
Wolfgang Beyer40530b682022-05-17 13:02:0115import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
Wolfgang Beyer7f495ee2022-07-08 13:45:1316import * as IconButton from '../../../ui/components/icon_button/icon_button.js';
Jack Franklin1f19d522022-07-06 14:00:1817import * as Input from '../../../ui/components/input/input.js';
Wolfgang Beyer40530b682022-05-17 13:02:0118import * as UI from '../../../ui/legacy/legacy.js';
19import * as LitHtml from '../../../ui/lit-html/lit-html.js';
Wolfgang Beyer185fb1f2022-08-01 13:48:2520import * as Sources from '../../sources/sources.js';
Wolfgang Beyerd2286472022-09-02 18:32:3021
Wolfgang Beyer65a82282022-09-05 08:22:3722import {type RequestHeaderSectionData, RequestHeaderSection} from './RequestHeaderSection.js';
Wolfgang Beyerd2286472022-09-02 18:32:3023import {type ResponseHeaderSectionData, ResponseHeaderSection} from './ResponseHeaderSection.js';
Wolfgang Beyer40530b682022-05-17 13:02:0124
25import requestHeadersViewStyles from './RequestHeadersView.css.js';
26
Wolfgang Beyerd1dd7962022-05-24 15:48:4827const RAW_HEADER_CUTOFF = 3000;
Wolfgang Beyer40530b682022-05-17 13:02:0128const {render, html} = LitHtml;
29
30const UIStrings = {
31 /**
32 *@description Text in Request Headers View of the Network panel
33 */
Wolfgang Beyer37fcd282022-06-29 09:34:1734 fromDiskCache: '(from disk cache)',
35 /**
36 *@description Text in Request Headers View of the Network panel
37 */
Wolfgang Beyer40530b682022-05-17 13:02:0138 fromMemoryCache: '(from memory cache)',
39 /**
40 *@description Text in Request Headers View of the Network panel
41 */
Wolfgang Beyer37fcd282022-06-29 09:34:1742 fromPrefetchCache: '(from prefetch cache)',
43 /**
44 *@description Text in Request Headers View of the Network panel
45 */
Wolfgang Beyer40530b682022-05-17 13:02:0146 fromServiceWorker: '(from `service worker`)',
47 /**
48 *@description Text in Request Headers View of the Network panel
49 */
50 fromSignedexchange: '(from signed-exchange)',
51 /**
52 *@description Text in Request Headers View of the Network panel
53 */
Wolfgang Beyer40530b682022-05-17 13:02:0154 fromWebBundle: '(from Web Bundle)',
55 /**
56 *@description Section header for a list of the main aspects of a http request
57 */
58 general: 'General',
59 /**
Wolfgang Beyer185fb1f2022-08-01 13:48:2560 *@description Label for a link from the network panel's headers view to the file in which
61 * header overrides are defined in the sources panel.
62 */
63 headerOverrides: 'Header overrides',
64 /**
Wolfgang Beyer235544c2022-05-24 08:07:4565 *@description Label for a checkbox to switch between raw and parsed headers
66 */
67 raw: 'Raw',
68 /**
69 *@description Text in Request Headers View of the Network panel
70 */
Wolfgang Beyer37fcd282022-06-29 09:34:1771 referrerPolicy: 'Referrer Policy',
Wolfgang Beyer235544c2022-05-24 08:07:4572 /**
Wolfgang Beyer37fcd282022-06-29 09:34:1773 *@description Text in Network Log View Columns of the Network panel
Wolfgang Beyer40530b682022-05-17 13:02:0174 */
Wolfgang Beyer37fcd282022-06-29 09:34:1775 remoteAddress: 'Remote Address',
76 /**
77 *@description Text in Request Headers View of the Network panel
78 */
79 requestHeaders: 'Request Headers',
Wolfgang Beyer40530b682022-05-17 13:02:0180 /**
81 *@description The HTTP method of a request
82 */
83 requestMethod: 'Request Method',
84 /**
Wolfgang Beyer37fcd282022-06-29 09:34:1785 *@description The URL of a request
86 */
87 requestUrl: 'Request URL',
88 /**
Wolfgang Beyer235544c2022-05-24 08:07:4589 *@description A context menu item in the Network Log View Columns of the Network panel
90 */
91 responseHeaders: 'Response Headers',
92 /**
Wolfgang Beyerd1dd7962022-05-24 15:48:4893 *@description Text to show more content
94 */
95 showMore: 'Show more',
96 /**
Wolfgang Beyer40530b682022-05-17 13:02:0197 *@description HTTP response code
98 */
99 statusCode: 'Status Code',
Wolfgang Beyer40530b682022-05-17 13:02:01100};
101const str_ = i18n.i18n.registerUIStrings('panels/network/components/RequestHeadersView.ts', UIStrings);
102const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
103
104export class RequestHeadersView extends UI.Widget.VBox {
Wolfgang Beyerd2286472022-09-02 18:32:30105 readonly #requestHeadersComponent = new RequestHeadersComponent();
Wolfgang Beyer40530b682022-05-17 13:02:01106 readonly #request: SDK.NetworkRequest.NetworkRequest;
107
108 constructor(request: SDK.NetworkRequest.NetworkRequest) {
109 super();
110 this.#request = request;
Wolfgang Beyerd2286472022-09-02 18:32:30111 this.contentElement.appendChild(this.#requestHeadersComponent);
Wolfgang Beyer40530b682022-05-17 13:02:01112 }
113
114 wasShown(): void {
115 this.#request.addEventListener(SDK.NetworkRequest.Events.RemoteAddressChanged, this.#refreshHeadersView, this);
116 this.#request.addEventListener(SDK.NetworkRequest.Events.FinishedLoading, this.#refreshHeadersView, this);
Wolfgang Beyer87150062022-07-29 10:45:47117 this.#request.addEventListener(SDK.NetworkRequest.Events.RequestHeadersChanged, this.#refreshHeadersView, this);
118 this.#request.addEventListener(SDK.NetworkRequest.Events.ResponseHeadersChanged, this.#refreshHeadersView, this);
Wolfgang Beyer40530b682022-05-17 13:02:01119 this.#refreshHeadersView();
120 }
121
122 willHide(): void {
123 this.#request.removeEventListener(SDK.NetworkRequest.Events.RemoteAddressChanged, this.#refreshHeadersView, this);
124 this.#request.removeEventListener(SDK.NetworkRequest.Events.FinishedLoading, this.#refreshHeadersView, this);
Wolfgang Beyer87150062022-07-29 10:45:47125 this.#request.removeEventListener(SDK.NetworkRequest.Events.RequestHeadersChanged, this.#refreshHeadersView, this);
126 this.#request.removeEventListener(SDK.NetworkRequest.Events.ResponseHeadersChanged, this.#refreshHeadersView, this);
Wolfgang Beyer40530b682022-05-17 13:02:01127 }
128
129 #refreshHeadersView(): void {
Wolfgang Beyerd2286472022-09-02 18:32:30130 this.#requestHeadersComponent.data = {
Wolfgang Beyer40530b682022-05-17 13:02:01131 request: this.#request,
132 };
133 }
Wolfgang Beyer40426d82022-08-03 11:08:07134
135 revealHeader(section: NetworkForward.UIRequestLocation.UIHeaderSection, header?: string): void {
Wolfgang Beyerd2286472022-09-02 18:32:30136 this.#requestHeadersComponent.data = {
Wolfgang Beyer40426d82022-08-03 11:08:07137 request: this.#request,
138 toReveal: {section, header: header},
139 };
140 }
Wolfgang Beyer40530b682022-05-17 13:02:01141}
142
143export interface RequestHeadersComponentData {
144 request: SDK.NetworkRequest.NetworkRequest;
Wolfgang Beyer40426d82022-08-03 11:08:07145 toReveal?: {section: NetworkForward.UIRequestLocation.UIHeaderSection, header?: string};
Wolfgang Beyer40530b682022-05-17 13:02:01146}
147
148export class RequestHeadersComponent extends HTMLElement {
149 static readonly litTagName = LitHtml.literal`devtools-request-headers`;
150 readonly #shadow = this.attachShadow({mode: 'open'});
151 #request?: Readonly<SDK.NetworkRequest.NetworkRequest>;
Wolfgang Beyer235544c2022-05-24 08:07:45152 #showResponseHeadersText = false;
153 #showRequestHeadersText = false;
Wolfgang Beyerd1dd7962022-05-24 15:48:48154 #showResponseHeadersTextFull = false;
155 #showRequestHeadersTextFull = false;
Wolfgang Beyer40426d82022-08-03 11:08:07156 #toReveal?: {section: NetworkForward.UIRequestLocation.UIHeaderSection, header?: string} = undefined;
Wolfgang Beyer185fb1f2022-08-01 13:48:25157 readonly #workspace = Workspace.Workspace.WorkspaceImpl.instance();
Wolfgang Beyer40530b682022-05-17 13:02:01158
159 set data(data: RequestHeadersComponentData) {
160 this.#request = data.request;
Wolfgang Beyer40426d82022-08-03 11:08:07161 this.#toReveal = data.toReveal;
Wolfgang Beyer40530b682022-05-17 13:02:01162 this.#render();
163 }
164
165 connectedCallback(): void {
166 this.#shadow.adoptedStyleSheets = [requestHeadersViewStyles];
Wolfgang Beyer185fb1f2022-08-01 13:48:25167 this.#workspace.addEventListener(
168 Workspace.Workspace.Events.UISourceCodeAdded, this.#uiSourceCodeAddedOrRemoved, this);
169 this.#workspace.addEventListener(
170 Workspace.Workspace.Events.UISourceCodeRemoved, this.#uiSourceCodeAddedOrRemoved, this);
171 }
172
173 disconnectedCallback(): void {
174 this.#workspace.removeEventListener(
175 Workspace.Workspace.Events.UISourceCodeAdded, this.#uiSourceCodeAddedOrRemoved, this);
176 this.#workspace.removeEventListener(
177 Workspace.Workspace.Events.UISourceCodeRemoved, this.#uiSourceCodeAddedOrRemoved, this);
178 }
179
180 #uiSourceCodeAddedOrRemoved(event: Common.EventTarget.EventTargetEvent<Workspace.UISourceCode.UISourceCode>): void {
181 if (this.#getHeaderOverridesFileUrl() === event.data.url()) {
182 this.#render();
183 }
Wolfgang Beyer40530b682022-05-17 13:02:01184 }
185
186 #render(): void {
Wolfgang Beyer3e62bd72022-07-28 14:26:48187 if (!this.#request) {
188 return;
189 }
Wolfgang Beyer40530b682022-05-17 13:02:01190
191 // Disabled until https://crbug.com/1079231 is fixed.
192 // clang-format off
193 render(html`
194 ${this.#renderGeneralSection()}
Wolfgang Beyer235544c2022-05-24 08:07:45195 ${this.#renderResponseHeaders()}
196 ${this.#renderRequestHeaders()}
Wolfgang Beyer40530b682022-05-17 13:02:01197 `, this.#shadow, {host: this});
198 // clang-format on
199 }
200
Wolfgang Beyer3e62bd72022-07-28 14:26:48201 #renderResponseHeaders(): LitHtml.LitTemplate {
202 if (!this.#request) {
203 return LitHtml.nothing;
204 }
Wolfgang Beyer235544c2022-05-24 08:07:45205
206 const toggleShowRaw = (): void => {
207 this.#showResponseHeadersText = !this.#showResponseHeadersText;
208 this.#render();
209 };
210
211 // Disabled until https://crbug.com/1079231 is fixed.
212 // clang-format off
213 return html`
214 <${Category.litTagName}
215 @togglerawevent=${toggleShowRaw}
216 .data=${{
217 name: 'responseHeaders',
218 title: i18nString(UIStrings.responseHeaders),
219 headerCount: this.#request.sortedResponseHeaders.length,
220 checked: this.#request.responseHeadersText ? this.#showResponseHeadersText : undefined,
Wolfgang Beyer185fb1f2022-08-01 13:48:25221 additionalContent: this.#renderHeaderOverridesLink(),
Wolfgang Beyer40426d82022-08-03 11:08:07222 forceOpen: this.#toReveal?.section === NetworkForward.UIRequestLocation.UIHeaderSection.Response,
Wolfgang Beyer235544c2022-05-24 08:07:45223 } as CategoryData}
224 aria-label=${i18nString(UIStrings.responseHeaders)}
225 >
Wolfgang Beyerd1dd7962022-05-24 15:48:48226 ${this.#showResponseHeadersText ?
227 this.#renderRawHeaders(this.#request.responseHeadersText, true) : html`
Wolfgang Beyerd2286472022-09-02 18:32:30228 <${ResponseHeaderSection.litTagName} .data=${{
229 request: this.#request,
230 toReveal: this.#toReveal,
231 } as ResponseHeaderSectionData}></${ResponseHeaderSection.litTagName}>
Wolfgang Beyer235544c2022-05-24 08:07:45232 `}
233 </${Category.litTagName}>
234 `;
Wolfgang Beyerbcf4a832022-07-25 12:34:47235 // clang-format on
Wolfgang Beyer235544c2022-05-24 08:07:45236 }
237
Wolfgang Beyer185fb1f2022-08-01 13:48:25238 #renderHeaderOverridesLink(): LitHtml.LitTemplate {
239 const overrideable = Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.HEADER_OVERRIDES);
240 if (!overrideable || !this.#workspace.uiSourceCodeForURL(this.#getHeaderOverridesFileUrl())) {
241 return LitHtml.nothing;
242 }
243
244 const overridesSetting: Common.Settings.Setting<boolean> =
245 Common.Settings.Settings.instance().moduleSetting('persistenceNetworkOverridesEnabled');
246 // Disabled until https://crbug.com/1079231 is fixed.
247 // clang-format off
248 const fileIcon = overridesSetting.get() ? html`
249 <${IconButton.Icon.Icon.litTagName} class="inline-icon purple-dot" .data=${{
250 iconName: 'file-sync_icon',
251 width: '11px',
252 height: '13px',
253 } as IconButton.Icon.IconData}>
254 </${IconButton.Icon.Icon.litTagName}>` : html`
255 <${IconButton.Icon.Icon.litTagName} class="inline-icon" .data=${{
256 iconName: 'file_icon',
257 color: 'var(--color-text-primary)',
258 width: '12px',
259 height: '12px',
260 } as IconButton.Icon.IconData}>
261 </${IconButton.Icon.Icon.litTagName}>`;
262 // clang-format on
263
264 const revealHeadersFile = (event: Event): void => {
265 event.preventDefault();
266 const uiSourceCode = this.#workspace.uiSourceCodeForURL(this.#getHeaderOverridesFileUrl());
267 if (uiSourceCode) {
268 Sources.SourcesPanel.SourcesPanel.instance().showUISourceCode(uiSourceCode);
269 }
270 };
271
272 return html`
273 <x-link @click=${revealHeadersFile} class="link devtools-link">
274 ${fileIcon}${i18nString(UIStrings.headerOverrides)}
275 </x-link>
276 `;
277 }
278
279 #getHeaderOverridesFileUrl(): Platform.DevToolsPath.UrlString {
280 if (!this.#request) {
281 return Platform.DevToolsPath.EmptyUrlString;
282 }
283 const fileUrl = Persistence.NetworkPersistenceManager.NetworkPersistenceManager.instance().fileUrlFromNetworkUrl(
284 this.#request.url(), /* ignoreInactive */ true);
285 return fileUrl.substring(0, fileUrl.lastIndexOf('/')) + '/' +
286 Persistence.NetworkPersistenceManager.HEADERS_FILENAME as Platform.DevToolsPath.UrlString;
287 }
288
Wolfgang Beyer3e62bd72022-07-28 14:26:48289 #renderRequestHeaders(): LitHtml.LitTemplate {
290 if (!this.#request) {
291 return LitHtml.nothing;
292 }
Wolfgang Beyer7f495ee2022-07-08 13:45:13293 const requestHeadersText = this.#request.requestHeadersText();
294
Wolfgang Beyer235544c2022-05-24 08:07:45295 const toggleShowRaw = (): void => {
296 this.#showRequestHeadersText = !this.#showRequestHeadersText;
297 this.#render();
298 };
299
Wolfgang Beyer235544c2022-05-24 08:07:45300 // Disabled until https://crbug.com/1079231 is fixed.
301 // clang-format off
302 return html`
303 <${Category.litTagName}
304 @togglerawevent=${toggleShowRaw}
305 .data=${{
306 name: 'requestHeaders',
307 title: i18nString(UIStrings.requestHeaders),
308 headerCount: this.#request.requestHeaders().length,
309 checked: requestHeadersText? this.#showRequestHeadersText : undefined,
Wolfgang Beyer40426d82022-08-03 11:08:07310 forceOpen: this.#toReveal?.section === NetworkForward.UIRequestLocation.UIHeaderSection.Request,
Wolfgang Beyer235544c2022-05-24 08:07:45311 } as CategoryData}
312 aria-label=${i18nString(UIStrings.requestHeaders)}
313 >
Wolfgang Beyerd1dd7962022-05-24 15:48:48314 ${(this.#showRequestHeadersText && requestHeadersText) ?
315 this.#renderRawHeaders(requestHeadersText, false) : html`
Wolfgang Beyer65a82282022-09-05 08:22:37316 <${RequestHeaderSection.litTagName} .data=${{
317 request: this.#request,
318 toReveal: this.#toReveal,
319 } as RequestHeaderSectionData}></${RequestHeaderSection.litTagName}>
Wolfgang Beyer235544c2022-05-24 08:07:45320 `}
321 </${Category.litTagName}>
322 `;
Wolfgang Beyerbcf4a832022-07-25 12:34:47323 // clang-format on
Wolfgang Beyer235544c2022-05-24 08:07:45324 }
325
Wolfgang Beyerd1dd7962022-05-24 15:48:48326 #renderRawHeaders(rawHeadersText: string, forResponseHeaders: boolean): LitHtml.TemplateResult {
327 const trimmed = rawHeadersText.trim();
328 const showFull = forResponseHeaders ? this.#showResponseHeadersTextFull : this.#showRequestHeadersTextFull;
329 const isShortened = !showFull && trimmed.length > RAW_HEADER_CUTOFF;
330
331 const showMore = ():void => {
332 if (forResponseHeaders) {
333 this.#showResponseHeadersTextFull = true;
334 } else {
335 this.#showRequestHeadersTextFull = true;
336 }
337 this.#render();
338 };
339
340 const onContextMenuOpen = (event: Event): void => {
341 const showFull = forResponseHeaders ? this.#showResponseHeadersTextFull : this.#showRequestHeadersTextFull;
342 if (!showFull) {
343 const contextMenu = new UI.ContextMenu.ContextMenu(event);
344 const section = contextMenu.newSection();
345 section.appendItem(i18nString(UIStrings.showMore), showMore);
346 void contextMenu.show();
347 }
348 };
349
350 const addContextMenuListener = (el: Element):void => {
351 if (isShortened) {
352 el.addEventListener('contextmenu', onContextMenuOpen);
353 }
354 };
355
356 return html`
357 <div class="row raw-headers-row" on-render=${ComponentHelpers.Directives.nodeRenderedCallback(addContextMenuListener)}>
358 <div class="raw-headers">${isShortened ? trimmed.substring(0, RAW_HEADER_CUTOFF) : trimmed}</div>
359 ${isShortened ? html`
360 <${Buttons.Button.Button.litTagName}
361 .size=${Buttons.Button.Size.SMALL}
362 .variant=${Buttons.Button.Variant.SECONDARY}
363 @click=${showMore}
364 >${i18nString(UIStrings.showMore)}</${Buttons.Button.Button.litTagName}>
365 ` : LitHtml.nothing}
366 </div>
367 `;
368 }
369
Wolfgang Beyer3e62bd72022-07-28 14:26:48370 #renderGeneralSection(): LitHtml.LitTemplate {
371 if (!this.#request) {
372 return LitHtml.nothing;
373 }
Wolfgang Beyer40530b682022-05-17 13:02:01374
Wolfgang Beyer40426d82022-08-03 11:08:07375 const statusClasses = [];
Wolfgang Beyer40530b682022-05-17 13:02:01376 if (this.#request.statusCode < 300 || this.#request.statusCode === 304) {
Wolfgang Beyer40426d82022-08-03 11:08:07377 statusClasses.push('green-circle');
Wolfgang Beyer40530b682022-05-17 13:02:01378 } else if (this.#request.statusCode < 400) {
Wolfgang Beyer40426d82022-08-03 11:08:07379 statusClasses.push('yellow-circle');
380 } else {
381 statusClasses.push('red-circle');
Wolfgang Beyer40530b682022-05-17 13:02:01382 }
383
384 let statusText = this.#request.statusCode + ' ' + this.#request.statusText;
Wolfgang Beyer40530b682022-05-17 13:02:01385 if (this.#request.cachedInMemory()) {
386 statusText += ' ' + i18nString(UIStrings.fromMemoryCache);
Wolfgang Beyer40426d82022-08-03 11:08:07387 statusClasses.push('status-with-comment');
Wolfgang Beyer40530b682022-05-17 13:02:01388 } else if (this.#request.fetchedViaServiceWorker) {
389 statusText += ' ' + i18nString(UIStrings.fromServiceWorker);
Wolfgang Beyer40426d82022-08-03 11:08:07390 statusClasses.push('status-with-comment');
Wolfgang Beyer40530b682022-05-17 13:02:01391 } else if (this.#request.redirectSourceSignedExchangeInfoHasNoErrors()) {
392 statusText += ' ' + i18nString(UIStrings.fromSignedexchange);
Wolfgang Beyer40426d82022-08-03 11:08:07393 statusClasses.push('status-with-comment');
Wolfgang Beyer40530b682022-05-17 13:02:01394 } else if (this.#request.webBundleInnerRequestInfo()) {
395 statusText += ' ' + i18nString(UIStrings.fromWebBundle);
Wolfgang Beyer40426d82022-08-03 11:08:07396 statusClasses.push('status-with-comment');
Wolfgang Beyer40530b682022-05-17 13:02:01397 } else if (this.#request.fromPrefetchCache()) {
398 statusText += ' ' + i18nString(UIStrings.fromPrefetchCache);
Wolfgang Beyer40426d82022-08-03 11:08:07399 statusClasses.push('status-with-comment');
Wolfgang Beyer40530b682022-05-17 13:02:01400 } else if (this.#request.cached()) {
401 statusText += ' ' + i18nString(UIStrings.fromDiskCache);
Wolfgang Beyer40426d82022-08-03 11:08:07402 statusClasses.push('status-with-comment');
Wolfgang Beyer40530b682022-05-17 13:02:01403 }
404
405 // Disabled until https://crbug.com/1079231 is fixed.
406 // clang-format off
407 return html`
Wolfgang Beyer235544c2022-05-24 08:07:45408 <${Category.litTagName}
Wolfgang Beyer40426d82022-08-03 11:08:07409 .data=${{
410 name: 'general',
411 title: i18nString(UIStrings.general),
412 forceOpen: this.#toReveal?.section === NetworkForward.UIRequestLocation.UIHeaderSection.General,
413 } as CategoryData}
Wolfgang Beyer235544c2022-05-24 08:07:45414 aria-label=${i18nString(UIStrings.general)}
415 >
Wolfgang Beyer40426d82022-08-03 11:08:07416 ${this.#renderGeneralRow(i18nString(UIStrings.requestUrl), this.#request.url())}
417 ${this.#request.statusCode? this.#renderGeneralRow(i18nString(UIStrings.requestMethod), this.#request.requestMethod) : LitHtml.nothing}
418 ${this.#request.statusCode? this.#renderGeneralRow(i18nString(UIStrings.statusCode), statusText, statusClasses) : LitHtml.nothing}
419 ${this.#request.remoteAddress()? this.#renderGeneralRow(i18nString(UIStrings.remoteAddress), this.#request.remoteAddress()) : LitHtml.nothing}
420 ${this.#request.referrerPolicy()? this.#renderGeneralRow(i18nString(UIStrings.referrerPolicy), String(this.#request.referrerPolicy())) : LitHtml.nothing}
Wolfgang Beyer40530b682022-05-17 13:02:01421 </${Category.litTagName}>
422 `;
423 // clang-format on
424 }
Wolfgang Beyer40426d82022-08-03 11:08:07425
426 #renderGeneralRow(name: Common.UIString.LocalizedString, value: string, classNames?: string[]): LitHtml.LitTemplate {
427 const isHighlighted = this.#toReveal?.section === NetworkForward.UIRequestLocation.UIHeaderSection.General &&
Wolfgang Beyerfd504ec2022-09-06 09:48:11428 name.toLowerCase() === this.#toReveal?.header?.toLowerCase();
Wolfgang Beyer40426d82022-08-03 11:08:07429 return html`
430 <div class="row ${isHighlighted ? 'header-highlight' : ''}">
431 <div class="header-name">${name}:</div>
432 <div
433 class="header-value ${classNames?.join(' ')}"
434 @copy=${(): void => Host.userMetrics.actionTaken(Host.UserMetrics.Action.NetworkPanelCopyValue)}
435 >${value}</div>
436 </div>
437 `;
438 }
Wolfgang Beyer40530b682022-05-17 13:02:01439}
440
Wolfgang Beyer235544c2022-05-24 08:07:45441export class ToggleRawHeadersEvent extends Event {
442 static readonly eventName = 'togglerawevent';
443
444 constructor() {
445 super(ToggleRawHeadersEvent.eventName, {});
446 }
447}
448
Wolfgang Beyer40530b682022-05-17 13:02:01449export interface CategoryData {
450 name: string;
451 title: Common.UIString.LocalizedString;
Wolfgang Beyer235544c2022-05-24 08:07:45452 headerCount?: number;
453 checked?: boolean;
Wolfgang Beyer185fb1f2022-08-01 13:48:25454 additionalContent?: LitHtml.LitTemplate;
Wolfgang Beyer40426d82022-08-03 11:08:07455 forceOpen?: boolean;
Wolfgang Beyer40530b682022-05-17 13:02:01456}
457
458export class Category extends HTMLElement {
459 static readonly litTagName = LitHtml.literal`devtools-request-headers-category`;
460 readonly #shadow = this.attachShadow({mode: 'open'});
461 #expandedSetting?: Common.Settings.Setting<boolean>;
462 #title: Common.UIString.LocalizedString = Common.UIString.LocalizedEmptyString;
Wolfgang Beyer235544c2022-05-24 08:07:45463 #headerCount?: number = undefined;
464 #checked: boolean|undefined = undefined;
Wolfgang Beyer185fb1f2022-08-01 13:48:25465 #additionalContent: LitHtml.LitTemplate|undefined = undefined;
Wolfgang Beyer40426d82022-08-03 11:08:07466 #forceOpen: boolean|undefined = undefined;
Wolfgang Beyer40530b682022-05-17 13:02:01467
468 connectedCallback(): void {
Jack Franklin1f19d522022-07-06 14:00:18469 this.#shadow.adoptedStyleSheets = [requestHeadersViewStyles, Input.checkboxStyles];
Wolfgang Beyer40530b682022-05-17 13:02:01470 }
471
472 set data(data: CategoryData) {
473 this.#title = data.title;
474 this.#expandedSetting =
475 Common.Settings.Settings.instance().createSetting('request-info-' + data.name + '-category-expanded', true);
Wolfgang Beyer235544c2022-05-24 08:07:45476 this.#headerCount = data.headerCount;
477 this.#checked = data.checked;
Wolfgang Beyer185fb1f2022-08-01 13:48:25478 this.#additionalContent = data.additionalContent;
Wolfgang Beyer40426d82022-08-03 11:08:07479 this.#forceOpen = data.forceOpen;
Wolfgang Beyer40530b682022-05-17 13:02:01480 this.#render();
481 }
482
Wolfgang Beyer235544c2022-05-24 08:07:45483 #onCheckboxToggle(): void {
484 this.dispatchEvent(new ToggleRawHeadersEvent());
485 }
486
Wolfgang Beyer40530b682022-05-17 13:02:01487 #render(): void {
Wolfgang Beyer40426d82022-08-03 11:08:07488 const isOpen = (this.#expandedSetting ? this.#expandedSetting.get() : true) || this.#forceOpen;
Wolfgang Beyer40530b682022-05-17 13:02:01489 // Disabled until https://crbug.com/1079231 is fixed.
490 // clang-format off
491 render(html`
Wolfgang Beyer235544c2022-05-24 08:07:45492 <details ?open=${isOpen} @toggle=${this.#onToggle}>
493 <summary class="header" @keydown=${this.#onSummaryKeyDown}>
Wolfgang Beyer185fb1f2022-08-01 13:48:25494 <div class="header-grid-container">
495 <div>
Wolfgang Beyerd2ea5672022-09-05 07:37:28496 ${this.#title}${this.#headerCount !== undefined ?
Wolfgang Beyer185fb1f2022-08-01 13:48:25497 html`<span class="header-count"> (${this.#headerCount})</span>` :
498 LitHtml.nothing
499 }
500 </div>
501 <div class="hide-when-closed">
502 ${this.#checked !== undefined ? html`
503 <label><input type="checkbox" .checked=${this.#checked} @change=${this.#onCheckboxToggle} />${i18nString(UIStrings.raw)}</label>
504 ` : LitHtml.nothing}
505 </div>
506 <div class="hide-when-closed">${this.#additionalContent}</div>
Wolfgang Beyer235544c2022-05-24 08:07:45507 </summary>
Wolfgang Beyer40530b682022-05-17 13:02:01508 <slot></slot>
509 </details>
510 `, this.#shadow, {host: this});
511 // clang-format on
512 }
513
514 #onSummaryKeyDown(event: KeyboardEvent): void {
515 if (!event.target) {
516 return;
517 }
518 const summaryElement = event.target as HTMLElement;
519 const detailsElement = summaryElement.parentElement as HTMLDetailsElement;
520 if (!detailsElement) {
521 throw new Error('<details> element is not found for a <summary> element');
522 }
523 switch (event.key) {
524 case 'ArrowLeft':
525 detailsElement.open = false;
526 break;
527 case 'ArrowRight':
528 detailsElement.open = true;
529 break;
530 }
531 }
532
533 #onToggle(event: Event): void {
534 this.#expandedSetting?.set((event.target as HTMLDetailsElement).open);
535 }
536}
537
538ComponentHelpers.CustomElements.defineComponent('devtools-request-headers', RequestHeadersComponent);
539ComponentHelpers.CustomElements.defineComponent('devtools-request-headers-category', Category);
540
541declare global {
542 // eslint-disable-next-line @typescript-eslint/no-unused-vars
543 interface HTMLElementTagNameMap {
544 'devtools-request-headers': RequestHeadersComponent;
545 'devtools-request-headers-category': Category;
546 }
547}