blob: ac274b312bc3606368d69aaa4aced43a8eb3652a [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 Beyer40530b682022-05-17 13:02:019import {assertNotNullOrUndefined} from '../../../core/platform/platform.js';
10import * as SDK from '../../../core/sdk/sdk.js';
Wolfgang Beyer7f495ee2022-07-08 13:45:1311import * as Protocol from '../../../generated/protocol.js';
12import * as IssuesManager from '../../../models/issues_manager/issues_manager.js';
Wolfgang Beyerbcf4a832022-07-25 12:34:4713import * as ClientVariations from '../../../third_party/chromium/client-variations/client-variations.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';
20
21import requestHeadersViewStyles from './RequestHeadersView.css.js';
22
Wolfgang Beyerd1dd7962022-05-24 15:48:4823const RAW_HEADER_CUTOFF = 3000;
Wolfgang Beyer40530b682022-05-17 13:02:0124const {render, html} = LitHtml;
25
26const UIStrings = {
27 /**
Wolfgang Beyerbcf4a832022-07-25 12:34:4728 *@description Comment used in decoded X-Client-Data HTTP header output in Headers View of the Network panel
29 */
30 activeClientExperimentVariation: 'Active `client experiment variation IDs`.',
31 /**
32 *@description Comment used in decoded X-Client-Data HTTP header output in Headers View of the Network panel
33 */
34 activeClientExperimentVariationIds: 'Active `client experiment variation IDs` that trigger server-side behavior.',
35 /**
Wolfgang Beyer7f495ee2022-07-08 13:45:1336 *@description Text in Headers View of the Network panel
37 */
38 chooseThisOptionIfTheResourceAnd:
39 'Choose this option if the resource and the document are served from the same site.',
40 /**
Wolfgang Beyerbcf4a832022-07-25 12:34:4741 *@description Text in Headers View of the Network panel for X-Client-Data HTTP headers
42 */
43 decoded: 'Decoded:',
44 /**
Wolfgang Beyer40530b682022-05-17 13:02:0145 *@description Text in Request Headers View of the Network panel
46 */
Wolfgang Beyer37fcd282022-06-29 09:34:1747 fromDiskCache: '(from disk cache)',
48 /**
49 *@description Text in Request Headers View of the Network panel
50 */
Wolfgang Beyer40530b682022-05-17 13:02:0151 fromMemoryCache: '(from memory cache)',
52 /**
53 *@description Text in Request Headers View of the Network panel
54 */
Wolfgang Beyer37fcd282022-06-29 09:34:1755 fromPrefetchCache: '(from prefetch cache)',
56 /**
57 *@description Text in Request Headers View of the Network panel
58 */
Wolfgang Beyer40530b682022-05-17 13:02:0159 fromServiceWorker: '(from `service worker`)',
60 /**
61 *@description Text in Request Headers View of the Network panel
62 */
63 fromSignedexchange: '(from signed-exchange)',
64 /**
65 *@description Text in Request Headers View of the Network panel
66 */
Wolfgang Beyer40530b682022-05-17 13:02:0167 fromWebBundle: '(from Web Bundle)',
68 /**
69 *@description Section header for a list of the main aspects of a http request
70 */
71 general: 'General',
72 /**
Wolfgang Beyer7f495ee2022-07-08 13:45:1373 *@description Text that is usually a hyperlink to more documentation
74 */
75 learnMore: 'Learn more',
76 /**
77 *@description Text for a link to the issues panel
78 */
79 learnMoreInTheIssuesTab: 'Learn more in the issues tab',
80 /**
Wolfgang Beyer235544c2022-05-24 08:07:4581 *@description Label for a checkbox to switch between raw and parsed headers
82 */
83 raw: 'Raw',
84 /**
Wolfgang Beyer7f495ee2022-07-08 13:45:1385 *@description Text in Headers View of the Network panel
86 */
87 onlyChooseThisOptionIfAn:
88 'Only choose this option if an arbitrary website including this resource does not impose a security risk.',
89 /**
Wolfgang Beyerd0ab3a62022-07-12 12:59:0290 *@description Message to explain lack of raw headers for a particular network request
91 */
92 provisionalHeadersAreShownDisableCache: 'Provisional headers are shown. Disable cache to see full headers.',
93 /**
94 *@description Tooltip to explain lack of raw headers for a particular network request
95 */
96 onlyProvisionalHeadersAre:
97 'Only provisional headers are available because this request was not sent over the network and instead was served from a local cache, which doesn’t store the original request headers. Disable cache to see full request headers.',
98 /**
99 *@description Message to explain lack of raw headers for a particular network request
100 */
101 provisionalHeadersAreShown: 'Provisional headers are shown.',
102 /**
Wolfgang Beyer235544c2022-05-24 08:07:45103 *@description Text in Request Headers View of the Network panel
104 */
Wolfgang Beyer37fcd282022-06-29 09:34:17105 referrerPolicy: 'Referrer Policy',
Wolfgang Beyer235544c2022-05-24 08:07:45106 /**
Wolfgang Beyer37fcd282022-06-29 09:34:17107 *@description Text in Network Log View Columns of the Network panel
Wolfgang Beyer40530b682022-05-17 13:02:01108 */
Wolfgang Beyer37fcd282022-06-29 09:34:17109 remoteAddress: 'Remote Address',
110 /**
111 *@description Text in Request Headers View of the Network panel
112 */
113 requestHeaders: 'Request Headers',
Wolfgang Beyer40530b682022-05-17 13:02:01114 /**
115 *@description The HTTP method of a request
116 */
117 requestMethod: 'Request Method',
118 /**
Wolfgang Beyer37fcd282022-06-29 09:34:17119 *@description The URL of a request
120 */
121 requestUrl: 'Request URL',
122 /**
Wolfgang Beyer235544c2022-05-24 08:07:45123 *@description A context menu item in the Network Log View Columns of the Network panel
124 */
125 responseHeaders: 'Response Headers',
126 /**
Wolfgang Beyerd1dd7962022-05-24 15:48:48127 *@description Text to show more content
128 */
129 showMore: 'Show more',
130 /**
Wolfgang Beyer40530b682022-05-17 13:02:01131 *@description HTTP response code
132 */
133 statusCode: 'Status Code',
Wolfgang Beyer7f495ee2022-07-08 13:45:13134 /**
135 *@description Text in Headers View of the Network panel
136 */
137 thisDocumentWasBlockedFrom:
138 'This document was blocked from loading in an `iframe` with a `sandbox` attribute because this document specified a cross-origin opener policy.',
139 /**
140 *@description Text in Headers View of the Network panel
141 */
142 toEmbedThisFrameInYourDocument:
143 'To embed this frame in your document, the response needs to enable the cross-origin embedder policy by specifying the following response header:',
144 /**
145 *@description Text in Headers View of the Network panel
146 */
147 toUseThisResourceFromADifferent:
148 'To use this resource from a different origin, the server needs to specify a cross-origin resource policy in the response headers:',
149 /**
150 *@description Text in Headers View of the Network panel
151 */
152 toUseThisResourceFromADifferentOrigin:
153 'To use this resource from a different origin, the server may relax the cross-origin resource policy response header:',
154 /**
155 *@description Text in Headers View of the Network panel
156 */
157 toUseThisResourceFromADifferentSite:
158 'To use this resource from a different site, the server may relax the cross-origin resource policy response header:',
Wolfgang Beyer40530b682022-05-17 13:02:01159};
160const str_ = i18n.i18n.registerUIStrings('panels/network/components/RequestHeadersView.ts', UIStrings);
161const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
Wolfgang Beyer7f495ee2022-07-08 13:45:13162const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_);
Wolfgang Beyer40530b682022-05-17 13:02:01163
164export class RequestHeadersView extends UI.Widget.VBox {
165 readonly #headersView = new RequestHeadersComponent();
166 readonly #request: SDK.NetworkRequest.NetworkRequest;
167
168 constructor(request: SDK.NetworkRequest.NetworkRequest) {
169 super();
170 this.#request = request;
171 this.contentElement.appendChild(this.#headersView);
172 }
173
174 wasShown(): void {
175 this.#request.addEventListener(SDK.NetworkRequest.Events.RemoteAddressChanged, this.#refreshHeadersView, this);
176 this.#request.addEventListener(SDK.NetworkRequest.Events.FinishedLoading, this.#refreshHeadersView, this);
177 this.#refreshHeadersView();
178 }
179
180 willHide(): void {
181 this.#request.removeEventListener(SDK.NetworkRequest.Events.RemoteAddressChanged, this.#refreshHeadersView, this);
182 this.#request.removeEventListener(SDK.NetworkRequest.Events.FinishedLoading, this.#refreshHeadersView, this);
183 }
184
185 #refreshHeadersView(): void {
186 this.#headersView.data = {
187 request: this.#request,
188 };
189 }
190}
191
192export interface RequestHeadersComponentData {
193 request: SDK.NetworkRequest.NetworkRequest;
194}
195
196export class RequestHeadersComponent extends HTMLElement {
197 static readonly litTagName = LitHtml.literal`devtools-request-headers`;
198 readonly #shadow = this.attachShadow({mode: 'open'});
199 #request?: Readonly<SDK.NetworkRequest.NetworkRequest>;
Wolfgang Beyer235544c2022-05-24 08:07:45200 #showResponseHeadersText = false;
201 #showRequestHeadersText = false;
Wolfgang Beyerd1dd7962022-05-24 15:48:48202 #showResponseHeadersTextFull = false;
203 #showRequestHeadersTextFull = false;
Wolfgang Beyer40530b682022-05-17 13:02:01204
205 set data(data: RequestHeadersComponentData) {
206 this.#request = data.request;
207 this.#render();
208 }
209
210 connectedCallback(): void {
211 this.#shadow.adoptedStyleSheets = [requestHeadersViewStyles];
212 }
213
214 #render(): void {
215 assertNotNullOrUndefined(this.#request);
216
217 // Disabled until https://crbug.com/1079231 is fixed.
218 // clang-format off
219 render(html`
220 ${this.#renderGeneralSection()}
Wolfgang Beyer235544c2022-05-24 08:07:45221 ${this.#renderResponseHeaders()}
222 ${this.#renderRequestHeaders()}
Wolfgang Beyer40530b682022-05-17 13:02:01223 `, this.#shadow, {host: this});
224 // clang-format on
225 }
226
Wolfgang Beyer235544c2022-05-24 08:07:45227 #renderResponseHeaders(): LitHtml.TemplateResult {
228 assertNotNullOrUndefined(this.#request);
229
Wolfgang Beyer7f495ee2022-07-08 13:45:13230 const headersWithIssues = [];
231 if (this.#request.wasBlocked()) {
232 const headerWithIssues =
233 BlockedReasonDetails.get((this.#request.blockedReason() as Protocol.Network.BlockedReason));
234 if (headerWithIssues) {
235 headersWithIssues.push(headerWithIssues);
236 }
237 }
238
239 function mergeHeadersWithIssues(
240 headers: SDK.NetworkRequest.NameValue[], headersWithIssues: HeaderDescriptor[]): HeaderDescriptor[] {
241 let i = 0, j = 0;
242 const result: HeaderDescriptor[] = [];
243 while (i < headers.length && j < headersWithIssues.length) {
244 if (headers[i].name < headersWithIssues[j].name) {
245 result.push({...headers[i++], headerNotSet: false});
246 } else if (headers[i].name > headersWithIssues[j].name) {
247 result.push({...headersWithIssues[j++], headerNotSet: true});
248 } else {
249 result.push({...headersWithIssues[j++], ...headers[i++], headerNotSet: false});
250 }
251 }
252 while (i < headers.length) {
253 result.push({...headers[i++], headerNotSet: false});
254 }
255 while (j < headersWithIssues.length) {
256 result.push({...headersWithIssues[j++], headerNotSet: true});
257 }
258 return result;
259 }
260
261 const mergedHeaders = mergeHeadersWithIssues(this.#request.sortedResponseHeaders.slice(), headersWithIssues);
262
Wolfgang Beyer235544c2022-05-24 08:07:45263 const toggleShowRaw = (): void => {
264 this.#showResponseHeadersText = !this.#showResponseHeadersText;
265 this.#render();
266 };
267
268 // Disabled until https://crbug.com/1079231 is fixed.
269 // clang-format off
270 return html`
271 <${Category.litTagName}
272 @togglerawevent=${toggleShowRaw}
273 .data=${{
274 name: 'responseHeaders',
275 title: i18nString(UIStrings.responseHeaders),
276 headerCount: this.#request.sortedResponseHeaders.length,
277 checked: this.#request.responseHeadersText ? this.#showResponseHeadersText : undefined,
278 } as CategoryData}
279 aria-label=${i18nString(UIStrings.responseHeaders)}
280 >
Wolfgang Beyerd1dd7962022-05-24 15:48:48281 ${this.#showResponseHeadersText ?
282 this.#renderRawHeaders(this.#request.responseHeadersText, true) : html`
Wolfgang Beyer7f495ee2022-07-08 13:45:13283 ${mergedHeaders.map(header => this.#renderHeader(header))}
Wolfgang Beyer235544c2022-05-24 08:07:45284 `}
285 </${Category.litTagName}>
286 `;
Wolfgang Beyerbcf4a832022-07-25 12:34:47287 // clang-format on
Wolfgang Beyer235544c2022-05-24 08:07:45288 }
289
290 #renderRequestHeaders(): LitHtml.TemplateResult {
291 assertNotNullOrUndefined(this.#request);
292
Wolfgang Beyer7f495ee2022-07-08 13:45:13293 const headers = this.#request.requestHeaders().slice();
294 headers.sort(function(a, b) {
295 return Platform.StringUtilities.compare(a.name.toLowerCase(), b.name.toLowerCase());
296 });
297 const requestHeadersText = this.#request.requestHeadersText();
298
Wolfgang Beyer235544c2022-05-24 08:07:45299 const toggleShowRaw = (): void => {
300 this.#showRequestHeadersText = !this.#showRequestHeadersText;
301 this.#render();
302 };
303
Wolfgang Beyer235544c2022-05-24 08:07:45304 // Disabled until https://crbug.com/1079231 is fixed.
305 // clang-format off
306 return html`
307 <${Category.litTagName}
308 @togglerawevent=${toggleShowRaw}
309 .data=${{
310 name: 'requestHeaders',
311 title: i18nString(UIStrings.requestHeaders),
312 headerCount: this.#request.requestHeaders().length,
313 checked: requestHeadersText? this.#showRequestHeadersText : undefined,
314 } as CategoryData}
315 aria-label=${i18nString(UIStrings.requestHeaders)}
316 >
Wolfgang Beyerd1dd7962022-05-24 15:48:48317 ${(this.#showRequestHeadersText && requestHeadersText) ?
318 this.#renderRawHeaders(requestHeadersText, false) : html`
Wolfgang Beyerd0ab3a62022-07-12 12:59:02319 ${this.#maybeRenderProvisionalHeadersWarning()}
Wolfgang Beyer7f495ee2022-07-08 13:45:13320 ${headers.map(header => this.#renderHeader({...header, headerNotSet: false}))}
Wolfgang Beyer235544c2022-05-24 08:07:45321 `}
322 </${Category.litTagName}>
323 `;
Wolfgang Beyerbcf4a832022-07-25 12:34:47324 // clang-format on
Wolfgang Beyer235544c2022-05-24 08:07:45325 }
326
Wolfgang Beyerd0ab3a62022-07-12 12:59:02327 #maybeRenderProvisionalHeadersWarning(): LitHtml.LitTemplate {
328 assertNotNullOrUndefined(this.#request);
329 if (this.#request.requestHeadersText() !== undefined) {
330 return LitHtml.nothing;
331 }
332
333 let cautionText;
334 let cautionTitle = '';
335 if (this.#request.cachedInMemory() || this.#request.cached()) {
336 cautionText = i18nString(UIStrings.provisionalHeadersAreShownDisableCache);
337 cautionTitle = i18nString(UIStrings.onlyProvisionalHeadersAre);
338 } else {
339 cautionText = i18nString(UIStrings.provisionalHeadersAreShown);
340 }
341 return html`
342 <div class="call-to-action">
343 <div class="call-to-action-body">
344 <div class="explanation" title=${cautionTitle}>
345 <${IconButton.Icon.Icon.litTagName} class="inline-icon" .data=${{
346 iconName: 'warning_icon',
347 width: '12px',
348 height: '12px',
349 } as IconButton.Icon.IconData}>
350 </${IconButton.Icon.Icon.litTagName}>
351 ${cautionText} <x-link href="https://developer.chrome.com/docs/devtools/network/reference/#provisional-headers" class="link">${i18nString(UIStrings.learnMore)}</x-link>
352 </div>
353 </div>
354 </div>
355 `;
356 }
357
Wolfgang Beyer7f495ee2022-07-08 13:45:13358 #renderHeader(header: HeaderDescriptor): LitHtml.TemplateResult {
Wolfgang Beyerbcf4a832022-07-25 12:34:47359 // Disabled until https://crbug.com/1079231 is fixed.
360 // clang-format off
Wolfgang Beyer7f495ee2022-07-08 13:45:13361 return html`
362 <div class="row">
Wolfgang Beyerbcf4a832022-07-25 12:34:47363 <div class="header-name">
364 ${header.headerNotSet ? html`
365 <div class="header-badge header-badge-text">
366 ${i18n.i18n.lockedString('not-set')}
367 </div>
368 ` : ''}${header.name}:
369 </div>
370 ${this.#renderHeaderValue(header)}
Wolfgang Beyer7f495ee2022-07-08 13:45:13371 </div>
372 ${this.#maybeRenderHeaderDetails(header.details)}
373 `;
Wolfgang Beyerbcf4a832022-07-25 12:34:47374 // clang-format on
375 }
376
377 #renderHeaderValue(header: HeaderDescriptor): LitHtml.TemplateResult {
378 const headerId = header.name.toLowerCase();
379 if (headerId === 'x-client-data') {
380 const data = ClientVariations.parseClientVariations(header.value?.toString() || '');
381 const output = ClientVariations.formatClientVariations(
382 data, i18nString(UIStrings.activeClientExperimentVariation),
383 i18nString(UIStrings.activeClientExperimentVariationIds));
384 return html`
385 <div class="header-value ${header.headerValueIncorrect ? 'header-warning' : ''}">
386 ${header.value?.toString() || ''}
387 <div>${i18nString(UIStrings.decoded)}</div>
388 <code>${output}</code>
389 </div>
390 `;
391 }
392
393 return html`
394 <div class="header-value ${header.headerValueIncorrect ? 'header-warning' : ''}">
395 ${header.value?.toString() || ''}
396 </div>
397 `;
Wolfgang Beyer7f495ee2022-07-08 13:45:13398 }
399
400 #maybeRenderHeaderDetails(headerDetails?: HeaderDetailsDescriptor): LitHtml.LitTemplate {
401 if (!headerDetails) {
402 return LitHtml.nothing;
403 }
404 return html`
Wolfgang Beyerd0ab3a62022-07-12 12:59:02405 <div class="call-to-action">
406 <div class="call-to-action-body">
407 <div class="explanation">${headerDetails.explanation()}</div>
408 ${headerDetails.examples.map(example => html`
409 <div class="example">
410 <code>${example.codeSnippet}</code>
411 ${example.comment ? html`
412 <span class="comment">${example.comment()}</span>
413 ` : ''}
414 </div>
415 `)}
416 ${this.#maybeRenderHeaderDetailsLink(headerDetails)}
Wolfgang Beyer7f495ee2022-07-08 13:45:13417 </div>
418 </div>
419 `;
420 }
421
422 #maybeRenderHeaderDetailsLink(headerDetails?: HeaderDetailsDescriptor): LitHtml.LitTemplate {
423 if (this.#request && IssuesManager.RelatedIssue.hasIssueOfCategory(this.#request, IssuesManager.Issue.IssueCategory.CrossOriginEmbedderPolicy)) {
424 const followLink = (): void => {
425 Host.userMetrics.issuesPanelOpenedFrom(Host.UserMetrics.IssueOpener.LearnMoreLinkCOEP);
426 if (this.#request) {
427 void IssuesManager.RelatedIssue.reveal(
428 this.#request, IssuesManager.Issue.IssueCategory.CrossOriginEmbedderPolicy);
429 }
430 };
431 return html`
432 <div class="devtools-link" @click=${followLink}>
433 <${IconButton.Icon.Icon.litTagName} class="inline-icon" .data=${{
434 iconName: 'issue-exclamation-icon',
435 color: 'var(--issue-color-yellow)',
436 width: '16px',
437 height: '16px',
438 } as IconButton.Icon.IconData}>
439 </${IconButton.Icon.Icon.litTagName}>
440 ${i18nString(UIStrings.learnMoreInTheIssuesTab)}
441 </div>
442 `;
443 }
444 if (headerDetails?.link) {
445 return html`
446 <x-link href=${headerDetails.link.url} class="link">
447 <${IconButton.Icon.Icon.litTagName} class="inline-icon" .data=${{
448 iconName: 'link_icon',
449 color: 'var(--color-link)',
450 width: '16px',
451 height: '16px',
452 } as IconButton.Icon.IconData}>
453 </${IconButton.Icon.Icon.litTagName}
454 >${i18nString(UIStrings.learnMore)}
455 </x-link>
456 `;
457 }
458 return LitHtml.nothing;
459 }
460
Wolfgang Beyerd1dd7962022-05-24 15:48:48461 #renderRawHeaders(rawHeadersText: string, forResponseHeaders: boolean): LitHtml.TemplateResult {
462 const trimmed = rawHeadersText.trim();
463 const showFull = forResponseHeaders ? this.#showResponseHeadersTextFull : this.#showRequestHeadersTextFull;
464 const isShortened = !showFull && trimmed.length > RAW_HEADER_CUTOFF;
465
466 const showMore = ():void => {
467 if (forResponseHeaders) {
468 this.#showResponseHeadersTextFull = true;
469 } else {
470 this.#showRequestHeadersTextFull = true;
471 }
472 this.#render();
473 };
474
475 const onContextMenuOpen = (event: Event): void => {
476 const showFull = forResponseHeaders ? this.#showResponseHeadersTextFull : this.#showRequestHeadersTextFull;
477 if (!showFull) {
478 const contextMenu = new UI.ContextMenu.ContextMenu(event);
479 const section = contextMenu.newSection();
480 section.appendItem(i18nString(UIStrings.showMore), showMore);
481 void contextMenu.show();
482 }
483 };
484
485 const addContextMenuListener = (el: Element):void => {
486 if (isShortened) {
487 el.addEventListener('contextmenu', onContextMenuOpen);
488 }
489 };
490
491 return html`
492 <div class="row raw-headers-row" on-render=${ComponentHelpers.Directives.nodeRenderedCallback(addContextMenuListener)}>
493 <div class="raw-headers">${isShortened ? trimmed.substring(0, RAW_HEADER_CUTOFF) : trimmed}</div>
494 ${isShortened ? html`
495 <${Buttons.Button.Button.litTagName}
496 .size=${Buttons.Button.Size.SMALL}
497 .variant=${Buttons.Button.Variant.SECONDARY}
498 @click=${showMore}
499 >${i18nString(UIStrings.showMore)}</${Buttons.Button.Button.litTagName}>
500 ` : LitHtml.nothing}
501 </div>
502 `;
503 }
504
Wolfgang Beyer40530b682022-05-17 13:02:01505 #renderGeneralSection(): LitHtml.TemplateResult {
506 assertNotNullOrUndefined(this.#request);
507
508 let coloredCircleClassName = 'red-circle';
509 if (this.#request.statusCode < 300 || this.#request.statusCode === 304) {
510 coloredCircleClassName = 'green-circle';
511 } else if (this.#request.statusCode < 400) {
512 coloredCircleClassName = 'yellow-circle';
513 }
514
515 let statusText = this.#request.statusCode + ' ' + this.#request.statusText;
516 let statusTextHasComment = false;
517 if (this.#request.cachedInMemory()) {
518 statusText += ' ' + i18nString(UIStrings.fromMemoryCache);
519 statusTextHasComment = true;
520 } else if (this.#request.fetchedViaServiceWorker) {
521 statusText += ' ' + i18nString(UIStrings.fromServiceWorker);
522 statusTextHasComment = true;
523 } else if (this.#request.redirectSourceSignedExchangeInfoHasNoErrors()) {
524 statusText += ' ' + i18nString(UIStrings.fromSignedexchange);
525 statusTextHasComment = true;
526 } else if (this.#request.webBundleInnerRequestInfo()) {
527 statusText += ' ' + i18nString(UIStrings.fromWebBundle);
528 statusTextHasComment = true;
529 } else if (this.#request.fromPrefetchCache()) {
530 statusText += ' ' + i18nString(UIStrings.fromPrefetchCache);
531 statusTextHasComment = true;
532 } else if (this.#request.cached()) {
533 statusText += ' ' + i18nString(UIStrings.fromDiskCache);
534 statusTextHasComment = true;
535 }
536
537 // Disabled until https://crbug.com/1079231 is fixed.
538 // clang-format off
539 return html`
Wolfgang Beyer235544c2022-05-24 08:07:45540 <${Category.litTagName}
541 .data=${{name: 'general', title: i18nString(UIStrings.general)} as CategoryData}
542 aria-label=${i18nString(UIStrings.general)}
543 >
Wolfgang Beyer40530b682022-05-17 13:02:01544 <div class="row">
545 <div class="header-name">${i18nString(UIStrings.requestUrl)}:</div>
546 <div class="header-value">${this.#request.url()}</div>
547 </div>
548 ${this.#request.statusCode? html`
549 <div class="row">
550 <div class="header-name">${i18nString(UIStrings.requestMethod)}:</div>
551 <div class="header-value">${this.#request.requestMethod}</div>
552 </div>
553 <div class="row">
554 <div class="header-name">${i18nString(UIStrings.statusCode)}:</div>
555 <div class="header-value ${coloredCircleClassName} ${statusTextHasComment ? 'status-with-comment' : ''}">${statusText}</div>
556 </div>
557 ` : ''}
558 ${this.#request.remoteAddress()? html`
559 <div class="row">
560 <div class="header-name">${i18nString(UIStrings.remoteAddress)}:</div>
561 <div class="header-value">${this.#request.remoteAddress()}</div>
562 </div>
563 ` : ''}
564 ${this.#request.referrerPolicy()? html`
565 <div class="row">
566 <div class="header-name">${i18nString(UIStrings.referrerPolicy)}:</div>
567 <div class="header-value">${this.#request.referrerPolicy()}</div>
568 </div>
569 ` : ''}
570 </${Category.litTagName}>
571 `;
572 // clang-format on
573 }
574}
575
Wolfgang Beyer235544c2022-05-24 08:07:45576export class ToggleRawHeadersEvent extends Event {
577 static readonly eventName = 'togglerawevent';
578
579 constructor() {
580 super(ToggleRawHeadersEvent.eventName, {});
581 }
582}
583
Wolfgang Beyer40530b682022-05-17 13:02:01584export interface CategoryData {
585 name: string;
586 title: Common.UIString.LocalizedString;
Wolfgang Beyer235544c2022-05-24 08:07:45587 headerCount?: number;
588 checked?: boolean;
Wolfgang Beyer40530b682022-05-17 13:02:01589}
590
591export class Category extends HTMLElement {
592 static readonly litTagName = LitHtml.literal`devtools-request-headers-category`;
593 readonly #shadow = this.attachShadow({mode: 'open'});
594 #expandedSetting?: Common.Settings.Setting<boolean>;
595 #title: Common.UIString.LocalizedString = Common.UIString.LocalizedEmptyString;
Wolfgang Beyer235544c2022-05-24 08:07:45596 #headerCount?: number = undefined;
597 #checked: boolean|undefined = undefined;
Wolfgang Beyer40530b682022-05-17 13:02:01598
599 connectedCallback(): void {
Jack Franklin1f19d522022-07-06 14:00:18600 this.#shadow.adoptedStyleSheets = [requestHeadersViewStyles, Input.checkboxStyles];
Wolfgang Beyer40530b682022-05-17 13:02:01601 }
602
603 set data(data: CategoryData) {
604 this.#title = data.title;
605 this.#expandedSetting =
606 Common.Settings.Settings.instance().createSetting('request-info-' + data.name + '-category-expanded', true);
Wolfgang Beyer235544c2022-05-24 08:07:45607 this.#headerCount = data.headerCount;
608 this.#checked = data.checked;
Wolfgang Beyer40530b682022-05-17 13:02:01609 this.#render();
610 }
611
Wolfgang Beyer235544c2022-05-24 08:07:45612 #onCheckboxToggle(): void {
613 this.dispatchEvent(new ToggleRawHeadersEvent());
614 }
615
Wolfgang Beyer40530b682022-05-17 13:02:01616 #render(): void {
Wolfgang Beyer235544c2022-05-24 08:07:45617 const isOpen = this.#expandedSetting ? this.#expandedSetting.get() : true;
Wolfgang Beyer40530b682022-05-17 13:02:01618 // Disabled until https://crbug.com/1079231 is fixed.
619 // clang-format off
620 render(html`
Wolfgang Beyer235544c2022-05-24 08:07:45621 <details ?open=${isOpen} @toggle=${this.#onToggle}>
622 <summary class="header" @keydown=${this.#onSummaryKeyDown}>
623 ${this.#title}${this.#headerCount ?
624 html`<span class="header-count"> (${this.#headerCount})</span>` :
625 LitHtml.nothing
626 }
627 ${this.#checked !== undefined ? html`
628 <span class="raw-checkbox-container">
629 <label>
630 <input type="checkbox" .checked=${this.#checked} @change=${this.#onCheckboxToggle} />
631 ${i18nString(UIStrings.raw)}
632 </label>
633 </span>
634 ` : LitHtml.nothing}
635 </summary>
Wolfgang Beyer40530b682022-05-17 13:02:01636 <slot></slot>
637 </details>
638 `, this.#shadow, {host: this});
639 // clang-format on
640 }
641
642 #onSummaryKeyDown(event: KeyboardEvent): void {
643 if (!event.target) {
644 return;
645 }
646 const summaryElement = event.target as HTMLElement;
647 const detailsElement = summaryElement.parentElement as HTMLDetailsElement;
648 if (!detailsElement) {
649 throw new Error('<details> element is not found for a <summary> element');
650 }
651 switch (event.key) {
652 case 'ArrowLeft':
653 detailsElement.open = false;
654 break;
655 case 'ArrowRight':
656 detailsElement.open = true;
657 break;
658 }
659 }
660
661 #onToggle(event: Event): void {
662 this.#expandedSetting?.set((event.target as HTMLDetailsElement).open);
663 }
664}
665
666ComponentHelpers.CustomElements.defineComponent('devtools-request-headers', RequestHeadersComponent);
667ComponentHelpers.CustomElements.defineComponent('devtools-request-headers-category', Category);
668
669declare global {
670 // eslint-disable-next-line @typescript-eslint/no-unused-vars
671 interface HTMLElementTagNameMap {
672 'devtools-request-headers': RequestHeadersComponent;
673 'devtools-request-headers-category': Category;
674 }
675}
Wolfgang Beyer7f495ee2022-07-08 13:45:13676
677interface HeaderDetailsDescriptor {
678 explanation: () => string;
679 examples: Array<{
680 codeSnippet: string,
681 comment?: (() => string),
682 }>;
683 link: {
684 url: string,
685 }|null;
686}
687
688interface HeaderDescriptor {
689 name: string;
690 value: Object|null;
691 headerValueIncorrect?: boolean|null;
692 details?: HeaderDetailsDescriptor;
693 headerNotSet: boolean|null;
694}
695
696const BlockedReasonDetails = new Map<Protocol.Network.BlockedReason, HeaderDescriptor>([
697 [
698 Protocol.Network.BlockedReason.CoepFrameResourceNeedsCoepHeader,
699 {
700 name: 'cross-origin-embedder-policy',
701 value: null,
702 headerValueIncorrect: null,
703 details: {
704 explanation: i18nLazyString(UIStrings.toEmbedThisFrameInYourDocument),
705 examples: [{codeSnippet: 'Cross-Origin-Embedder-Policy: require-corp', comment: undefined}],
706 link: {url: 'https://web.dev/coop-coep/'},
707 },
708 headerNotSet: null,
709 },
710 ],
711 [
712 Protocol.Network.BlockedReason.CorpNotSameOriginAfterDefaultedToSameOriginByCoep,
713 {
714 name: 'cross-origin-resource-policy',
715 value: null,
716 headerValueIncorrect: null,
717 details: {
718 explanation: i18nLazyString(UIStrings.toUseThisResourceFromADifferent),
719 examples: [
720 {
721 codeSnippet: 'Cross-Origin-Resource-Policy: same-site',
722 comment: i18nLazyString(UIStrings.chooseThisOptionIfTheResourceAnd),
723 },
724 {
725 codeSnippet: 'Cross-Origin-Resource-Policy: cross-origin',
726 comment: i18nLazyString(UIStrings.onlyChooseThisOptionIfAn),
727 },
728 ],
729 link: {url: 'https://web.dev/coop-coep/'},
730 },
731 headerNotSet: null,
732 },
733 ],
734 [
735 Protocol.Network.BlockedReason.CoopSandboxedIframeCannotNavigateToCoopPage,
736 {
737 name: 'cross-origin-opener-policy',
738 value: null,
739 headerValueIncorrect: false,
740 details: {
741 explanation: i18nLazyString(UIStrings.thisDocumentWasBlockedFrom),
742 examples: [],
743 link: {url: 'https://web.dev/coop-coep/'},
744 },
745 headerNotSet: null,
746 },
747 ],
748 [
749 Protocol.Network.BlockedReason.CorpNotSameSite,
750 {
751 name: 'cross-origin-resource-policy',
752 value: null,
753 headerValueIncorrect: true,
754 details: {
755 explanation: i18nLazyString(UIStrings.toUseThisResourceFromADifferentSite),
756 examples: [
757 {
758 codeSnippet: 'Cross-Origin-Resource-Policy: cross-origin',
759 comment: i18nLazyString(UIStrings.onlyChooseThisOptionIfAn),
760 },
761 ],
762 link: null,
763 },
764 headerNotSet: null,
765 },
766 ],
767 [
768 Protocol.Network.BlockedReason.CorpNotSameOrigin,
769 {
770 name: 'cross-origin-resource-policy',
771 value: null,
772 headerValueIncorrect: true,
773 details: {
774 explanation: i18nLazyString(UIStrings.toUseThisResourceFromADifferentOrigin),
775 examples: [
776 {
777 codeSnippet: 'Cross-Origin-Resource-Policy: same-site',
778 comment: i18nLazyString(UIStrings.chooseThisOptionIfTheResourceAnd),
779 },
780 {
781 codeSnippet: 'Cross-Origin-Resource-Policy: cross-origin',
782 comment: i18nLazyString(UIStrings.onlyChooseThisOptionIfAn),
783 },
784 ],
785 link: null,
786 },
787 headerNotSet: null,
788 },
789 ],
790]);