blob: 7500f8621449b3d64e1de8b5bc0c9f48cfc9be6f [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371/*
2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) IBM Corp. 2009 All rights reserved.
4 * Copyright (C) 2010 Google Inc. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16 * its contributors may be used to endorse or promote products derived
17 * from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31Network.RequestHeadersView = class extends UI.VBox {
32 /**
33 * @param {!SDK.NetworkRequest} request
34 */
35 constructor(request) {
36 super();
37 this.registerRequiredCSS('network/requestHeadersView.css');
38 this.element.classList.add('request-headers-view');
39
40 this._request = request;
41 this._decodeRequestParameters = true;
42 this._showRequestHeadersText = false;
43 this._showResponseHeadersText = false;
44
45 /** @type {?UI.TreeElement} */
46 this._highlightedElement = null;
47
48 const root = new UI.TreeOutlineInShadow();
49 root.registerRequiredCSS('network/requestHeadersTree.css');
50 root.element.classList.add('request-headers-tree');
51 root.setFocusable(false);
52 root.makeDense();
53 root.expandTreeElementsWhenArrowing = true;
54 this.element.appendChild(root.element);
55
56 const generalCategory = new Network.RequestHeadersView.Category(root, 'general', Common.UIString('General'));
57 generalCategory.hidden = false;
58 this._urlItem = generalCategory.createLeaf();
59 this._requestMethodItem = generalCategory.createLeaf();
60 this._statusCodeItem = generalCategory.createLeaf();
61 this._remoteAddressItem = generalCategory.createLeaf();
62 this._remoteAddressItem.hidden = true;
63 this._referrerPolicyItem = generalCategory.createLeaf();
64 this._referrerPolicyItem.hidden = true;
65
66 this._responseHeadersCategory = new Network.RequestHeadersView.Category(root, 'responseHeaders', '');
67 this._requestHeadersCategory = new Network.RequestHeadersView.Category(root, 'requestHeaders', '');
68 this._queryStringCategory = new Network.RequestHeadersView.Category(root, 'queryString', '');
69 this._formDataCategory = new Network.RequestHeadersView.Category(root, 'formData', '');
70 this._requestPayloadCategory =
71 new Network.RequestHeadersView.Category(root, 'requestPayload', Common.UIString('Request Payload'));
72 }
73
74 /**
75 * @override
76 */
77 wasShown() {
78 this._clearHighlight();
79 this._request.addEventListener(SDK.NetworkRequest.Events.RemoteAddressChanged, this._refreshRemoteAddress, this);
80 this._request.addEventListener(SDK.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this);
81 this._request.addEventListener(
82 SDK.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this);
83 this._request.addEventListener(SDK.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this);
84
85 this._refreshURL();
86 this._refreshQueryString();
87 this._refreshRequestHeaders();
88 this._refreshResponseHeaders();
89 this._refreshHTTPInformation();
90 this._refreshRemoteAddress();
91 this._refreshReferrerPolicy();
92 }
93
94 /**
95 * @override
96 */
97 willHide() {
98 this._request.removeEventListener(SDK.NetworkRequest.Events.RemoteAddressChanged, this._refreshRemoteAddress, this);
99 this._request.removeEventListener(
100 SDK.NetworkRequest.Events.RequestHeadersChanged, this._refreshRequestHeaders, this);
101 this._request.removeEventListener(
102 SDK.NetworkRequest.Events.ResponseHeadersChanged, this._refreshResponseHeaders, this);
103 this._request.removeEventListener(SDK.NetworkRequest.Events.FinishedLoading, this._refreshHTTPInformation, this);
104 }
105
106 /**
107 * @param {string} name
108 * @param {string} value
109 * @return {!DocumentFragment}
110 */
111 _formatHeader(name, value) {
112 const fragment = createDocumentFragment();
113 fragment.createChild('div', 'header-name').textContent = name + ': ';
114 fragment.createChild('span', 'header-separator');
115 fragment.createChild('div', 'header-value source-code').textContent = value;
116
117 return fragment;
118 }
119
120 /**
121 * @param {string} value
122 * @param {string} className
123 * @param {boolean} decodeParameters
124 * @return {!Element}
125 */
126 _formatParameter(value, className, decodeParameters) {
127 let errorDecoding = false;
128
129 if (decodeParameters) {
130 value = value.replace(/\+/g, ' ');
131 if (value.indexOf('%') >= 0) {
132 try {
133 value = decodeURIComponent(value);
134 } catch (e) {
135 errorDecoding = true;
136 }
137 }
138 }
139 const div = createElementWithClass('div', className);
140 if (value === '')
141 div.classList.add('empty-value');
142 if (errorDecoding)
143 div.createChild('span', 'header-decode-error').textContent = Common.UIString('(unable to decode value)');
144 else
145 div.textContent = value;
146 return div;
147 }
148
149 _refreshURL() {
150 this._urlItem.title = this._formatHeader(Common.UIString('Request URL'), this._request.url());
151 }
152
153 _refreshQueryString() {
154 const queryString = this._request.queryString();
155 const queryParameters = this._request.queryParameters;
156 this._queryStringCategory.hidden = !queryParameters;
157 if (queryParameters) {
158 this._refreshParams(
159 Common.UIString('Query String Parameters'), queryParameters, queryString, this._queryStringCategory);
160 }
161 }
162
163 async _refreshFormData() {
164 this._formDataCategory.hidden = true;
165 this._requestPayloadCategory.hidden = true;
166
167 const formData = await this._request.requestFormData();
168 if (!formData)
169 return;
170
171 const formParameters = await this._request.formParameters();
172 if (formParameters) {
173 this._formDataCategory.hidden = false;
174 this._refreshParams(Common.UIString('Form Data'), formParameters, formData, this._formDataCategory);
175 } else {
176 this._requestPayloadCategory.hidden = false;
177 try {
178 const json = JSON.parse(formData);
179 this._refreshRequestJSONPayload(json, formData);
180 } catch (e) {
181 this._populateTreeElementWithSourceText(this._requestPayloadCategory, formData);
182 }
183 }
184 }
185
186 /**
187 * @param {!UI.TreeElement} treeElement
188 * @param {?string} sourceText
189 */
190 _populateTreeElementWithSourceText(treeElement, sourceText) {
191 const max_len = 3000;
192 const text = (sourceText || '').trim();
193 const trim = text.length > max_len;
194
195 const sourceTextElement = createElementWithClass('span', 'header-value source-code');
196 sourceTextElement.textContent = trim ? text.substr(0, max_len) : text;
197
198 const sourceTreeElement = new UI.TreeElement(sourceTextElement);
199 sourceTreeElement.selectable = false;
200 treeElement.removeChildren();
201 treeElement.appendChild(sourceTreeElement);
202 if (!trim)
203 return;
204
205 const showMoreButton = createElementWithClass('button', 'request-headers-show-more-button');
206 showMoreButton.textContent = Common.UIString('Show more');
207 showMoreButton.addEventListener('click', () => {
208 showMoreButton.remove();
209 sourceTextElement.textContent = text;
210 });
211 sourceTextElement.appendChild(showMoreButton);
212 }
213
214 /**
215 * @param {string} title
216 * @param {?Array.<!SDK.NetworkRequest.NameValue>} params
217 * @param {?string} sourceText
218 * @param {!UI.TreeElement} paramsTreeElement
219 */
220 _refreshParams(title, params, sourceText, paramsTreeElement) {
221 paramsTreeElement.removeChildren();
222
223 paramsTreeElement.listItemElement.removeChildren();
224 paramsTreeElement.listItemElement.createTextChild(title);
225
226 const headerCount = createElementWithClass('span', 'header-count');
227 headerCount.textContent = Common.UIString('\u00A0(%d)', params.length);
228 paramsTreeElement.listItemElement.appendChild(headerCount);
229
230 /**
231 * @param {!Event} event
232 * @this {Network.RequestHeadersView}
233 */
234 function toggleViewSource(event) {
235 paramsTreeElement[Network.RequestHeadersView._viewSourceSymbol] =
236 !paramsTreeElement[Network.RequestHeadersView._viewSourceSymbol];
237 this._refreshParams(title, params, sourceText, paramsTreeElement);
238 event.consume();
239 }
240
241 paramsTreeElement.listItemElement.appendChild(this._createViewSourceToggle(
242 paramsTreeElement[Network.RequestHeadersView._viewSourceSymbol], toggleViewSource.bind(this)));
243
244 if (paramsTreeElement[Network.RequestHeadersView._viewSourceSymbol]) {
245 this._populateTreeElementWithSourceText(paramsTreeElement, sourceText);
246 return;
247 }
248
249 const toggleTitle =
250 this._decodeRequestParameters ? Common.UIString('view URL encoded') : Common.UIString('view decoded');
251 const toggleButton = this._createToggleButton(toggleTitle);
252 toggleButton.addEventListener('click', this._toggleURLDecoding.bind(this), false);
253 paramsTreeElement.listItemElement.appendChild(toggleButton);
254
255 for (let i = 0; i < params.length; ++i) {
256 const paramNameValue = createDocumentFragment();
257 if (params[i].name !== '') {
258 const name = this._formatParameter(params[i].name + ': ', 'header-name', this._decodeRequestParameters);
259 const value = this._formatParameter(params[i].value, 'header-value source-code', this._decodeRequestParameters);
260 paramNameValue.appendChild(name);
261 paramNameValue.createChild('span', 'header-separator');
262 paramNameValue.appendChild(value);
263 } else {
264 paramNameValue.appendChild(
265 this._formatParameter(Common.UIString('(empty)'), 'empty-request-header', this._decodeRequestParameters));
266 }
267
268 const paramTreeElement = new UI.TreeElement(paramNameValue);
269 paramTreeElement.selectable = false;
270 paramsTreeElement.appendChild(paramTreeElement);
271 }
272 }
273
274 /**
275 * @param {*} parsedObject
276 * @param {string} sourceText
277 */
278 _refreshRequestJSONPayload(parsedObject, sourceText) {
279 const treeElement = this._requestPayloadCategory;
280 treeElement.removeChildren();
281
282 const listItem = this._requestPayloadCategory.listItemElement;
283 listItem.removeChildren();
284 listItem.createTextChild(this._requestPayloadCategory.title);
285
286 /**
287 * @param {!Event} event
288 * @this {Network.RequestHeadersView}
289 */
290 function toggleViewSource(event) {
291 treeElement[Network.RequestHeadersView._viewSourceSymbol] =
292 !treeElement[Network.RequestHeadersView._viewSourceSymbol];
293 this._refreshRequestJSONPayload(parsedObject, sourceText);
294 event.consume();
295 }
296
297 listItem.appendChild(this._createViewSourceToggle(
298 treeElement[Network.RequestHeadersView._viewSourceSymbol], toggleViewSource.bind(this)));
299 if (treeElement[Network.RequestHeadersView._viewSourceSymbol]) {
300 this._populateTreeElementWithSourceText(this._requestPayloadCategory, sourceText);
301 } else {
302 const object = SDK.RemoteObject.fromLocalObject(parsedObject);
303 const section = new ObjectUI.ObjectPropertiesSection(object, object.description);
304 section.expand();
305 section.editable = false;
306 treeElement.appendChild(new UI.TreeElement(section.element));
307 }
308 }
309
310 /**
311 * @param {boolean} viewSource
312 * @param {function(!Event)} handler
313 * @return {!Element}
314 */
315 _createViewSourceToggle(viewSource, handler) {
316 const viewSourceToggleTitle = viewSource ? Common.UIString('view parsed') : Common.UIString('view source');
317 const viewSourceToggleButton = this._createToggleButton(viewSourceToggleTitle);
318 viewSourceToggleButton.addEventListener('click', handler, false);
319 return viewSourceToggleButton;
320 }
321
322 /**
323 * @param {!Event} event
324 */
325 _toggleURLDecoding(event) {
326 this._decodeRequestParameters = !this._decodeRequestParameters;
327 this._refreshQueryString();
328 this._refreshFormData();
329 event.consume();
330 }
331
332 _refreshRequestHeaders() {
333 const treeElement = this._requestHeadersCategory;
334 const headers = this._request.requestHeaders().slice();
335 headers.sort(function(a, b) {
336 return a.name.toLowerCase().compareTo(b.name.toLowerCase());
337 });
338 const headersText = this._request.requestHeadersText();
339
340 if (this._showRequestHeadersText && headersText)
341 this._refreshHeadersText(Common.UIString('Request Headers'), headers.length, headersText, treeElement);
342 else
343 this._refreshHeaders(Common.UIString('Request Headers'), headers, treeElement, headersText === undefined);
344
345 if (headersText) {
346 const toggleButton = this._createHeadersToggleButton(this._showRequestHeadersText);
347 toggleButton.addEventListener('click', this._toggleRequestHeadersText.bind(this), false);
348 treeElement.listItemElement.appendChild(toggleButton);
349 }
350
351 this._refreshFormData();
352 }
353
354 _refreshResponseHeaders() {
355 const treeElement = this._responseHeadersCategory;
356 const headers = this._request.sortedResponseHeaders.slice();
357 const headersText = this._request.responseHeadersText;
358
Joey Arhar7199a942019-09-10 22:37:39359 if (this._showResponseHeadersText) {
Blink Reformat4c46d092018-04-07 15:32:37360 this._refreshHeadersText(Common.UIString('Response Headers'), headers.length, headersText, treeElement);
Joey Arhar7199a942019-09-10 22:37:39361 } else {
362 this._refreshHeaders(
363 Common.UIString('Response Headers'), headers, treeElement, /* provisional */ false,
364 this._request.blockedResponseCookies());
365 }
Blink Reformat4c46d092018-04-07 15:32:37366
367 if (headersText) {
368 const toggleButton = this._createHeadersToggleButton(this._showResponseHeadersText);
369 toggleButton.addEventListener('click', this._toggleResponseHeadersText.bind(this), false);
370 treeElement.listItemElement.appendChild(toggleButton);
371 }
372 }
373
374 _refreshHTTPInformation() {
375 const requestMethodElement = this._requestMethodItem;
376 requestMethodElement.hidden = !this._request.statusCode;
377 const statusCodeElement = this._statusCodeItem;
378 statusCodeElement.hidden = !this._request.statusCode;
379
380 if (this._request.statusCode) {
381 const statusCodeFragment = createDocumentFragment();
Tsuyoshi Horo41dcffd2019-05-24 03:59:05382 statusCodeFragment.createChild('div', 'header-name').textContent = ls`Status Code` +
383 ': ';
Blink Reformat4c46d092018-04-07 15:32:37384 statusCodeFragment.createChild('span', 'header-separator');
385
Joel Einbinder7fbe24c2019-01-24 05:19:01386 const statusCodeImage = statusCodeFragment.createChild('span', 'resource-status-image', 'dt-icon-label');
Blink Reformat4c46d092018-04-07 15:32:37387 statusCodeImage.title = this._request.statusCode + ' ' + this._request.statusText;
388
389 if (this._request.statusCode < 300 || this._request.statusCode === 304)
390 statusCodeImage.type = 'smallicon-green-ball';
391 else if (this._request.statusCode < 400)
392 statusCodeImage.type = 'smallicon-orange-ball';
393 else
394 statusCodeImage.type = 'smallicon-red-ball';
395
Tsuyoshi Horo41dcffd2019-05-24 03:59:05396 requestMethodElement.title = this._formatHeader(ls`Request Method`, this._request.requestMethod);
Blink Reformat4c46d092018-04-07 15:32:37397
398 const statusTextElement = statusCodeFragment.createChild('div', 'header-value source-code');
399 let statusText = this._request.statusCode + ' ' + this._request.statusText;
Ben Kelly0375f502018-09-11 17:05:50400 if (this._request.cachedInMemory()) {
Tsuyoshi Horo41dcffd2019-05-24 03:59:05401 statusText += ' ' + ls`(from memory cache)`;
Ben Kelly0375f502018-09-11 17:05:50402 statusTextElement.classList.add('status-from-cache');
403 } else if (this._request.fetchedViaServiceWorker) {
Tsuyoshi Horo41dcffd2019-05-24 03:59:05404 statusText += ' ' + ls`(from ServiceWorker)`;
Blink Reformat4c46d092018-04-07 15:32:37405 statusTextElement.classList.add('status-from-cache');
Tsuyoshi Horo8f6a2b12018-10-01 22:24:03406 } else if (
407 this._request.redirectSource() && this._request.redirectSource().signedExchangeInfo() &&
408 !this._request.redirectSource().signedExchangeInfo().errors) {
Tsuyoshi Horo41dcffd2019-05-24 03:59:05409 statusText += ' ' + ls`(from signed-exchange)`;
410 statusTextElement.classList.add('status-from-cache');
411 } else if (this._request.fromPrefetchCache()) {
412 statusText += ' ' + ls`(from prefetch cache)`;
Tsuyoshi Horo02266c32018-05-21 17:01:18413 statusTextElement.classList.add('status-from-cache');
Blink Reformat4c46d092018-04-07 15:32:37414 } else if (this._request.cached()) {
Tsuyoshi Horo41dcffd2019-05-24 03:59:05415 statusText += ' ' + ls`(from disk cache)`;
Blink Reformat4c46d092018-04-07 15:32:37416 statusTextElement.classList.add('status-from-cache');
417 }
418 statusTextElement.textContent = statusText;
419
420 statusCodeElement.title = statusCodeFragment;
421 }
422 }
423
424 /**
425 * @param {string} title
426 * @param {!UI.TreeElement} headersTreeElement
427 * @param {number} headersLength
428 */
429 _refreshHeadersTitle(title, headersTreeElement, headersLength) {
430 headersTreeElement.listItemElement.removeChildren();
431 headersTreeElement.listItemElement.createTextChild(title);
432
433 const headerCount = Common.UIString('\u00A0(%d)', headersLength);
434 headersTreeElement.listItemElement.createChild('span', 'header-count').textContent = headerCount;
435 }
436
437 /**
438 * @param {string} title
439 * @param {!Array.<!SDK.NetworkRequest.NameValue>} headers
440 * @param {!UI.TreeElement} headersTreeElement
441 * @param {boolean=} provisionalHeaders
Joey Arhar7199a942019-09-10 22:37:39442 * @param {!Array<!SDK.NetworkRequest.BlockedSetCookieWithReason>=} blockedResponseCookies
Blink Reformat4c46d092018-04-07 15:32:37443 */
Joey Arhar7199a942019-09-10 22:37:39444 _refreshHeaders(title, headers, headersTreeElement, provisionalHeaders, blockedResponseCookies) {
Blink Reformat4c46d092018-04-07 15:32:37445 headersTreeElement.removeChildren();
446
447 const length = headers.length;
448 this._refreshHeadersTitle(title, headersTreeElement, length);
449
450 if (provisionalHeaders) {
451 const cautionText = Common.UIString('Provisional headers are shown');
452 const cautionFragment = createDocumentFragment();
Joel Einbinder7fbe24c2019-01-24 05:19:01453 cautionFragment.createChild('span', '', 'dt-icon-label').type = 'smallicon-warning';
Blink Reformat4c46d092018-04-07 15:32:37454 cautionFragment.createChild('div', 'caution').textContent = cautionText;
455 const cautionTreeElement = new UI.TreeElement(cautionFragment);
456 cautionTreeElement.selectable = false;
457 headersTreeElement.appendChild(cautionTreeElement);
458 }
459
Joey Arhar41a5fad2019-09-13 22:18:45460 /** @type {!Map<string, !Array<!Protocol.Network.SetCookieBlockedReason>>} */
461 const blockedCookieLineToReasons = new Map();
Joey Arhar7199a942019-09-10 22:37:39462 if (blockedResponseCookies) {
463 blockedResponseCookies.forEach(blockedCookie => {
Joey Arhar41a5fad2019-09-13 22:18:45464 blockedCookieLineToReasons.set(blockedCookie.cookieLine, blockedCookie.blockedReasons);
Joey Arhar7199a942019-09-10 22:37:39465 });
466 }
467
Blink Reformat4c46d092018-04-07 15:32:37468 headersTreeElement.hidden = !length && !provisionalHeaders;
469 for (let i = 0; i < length; ++i) {
470 const headerTreeElement = new UI.TreeElement(this._formatHeader(headers[i].name, headers[i].value));
471 headerTreeElement.selectable = false;
Blink Reformat4c46d092018-04-07 15:32:37472 headerTreeElement[Network.RequestHeadersView._headerNameSymbol] = headers[i].name;
Joey Arhar7199a942019-09-10 22:37:39473
474 if (headers[i].name.toLowerCase() === 'set-cookie') {
Joey Arhar41a5fad2019-09-13 22:18:45475 const matchingBlockedReasons = blockedCookieLineToReasons.get(headers[i].value);
476 if (matchingBlockedReasons) {
Joey Arhar7199a942019-09-10 22:37:39477 const icon = UI.Icon.create('smallicon-warning', '');
478 headerTreeElement.listItemElement.appendChild(icon);
Joey Arhar41a5fad2019-09-13 22:18:45479
480 let titleText = '';
481 for (const blockedReason of matchingBlockedReasons) {
482 if (titleText)
483 titleText += '\n';
484 titleText += SDK.NetworkRequest.setCookieBlockedReasonToUiString(blockedReason);
485 }
486 icon.title = titleText;
Joey Arhar7199a942019-09-10 22:37:39487 }
488 }
489
490 headersTreeElement.appendChild(headerTreeElement);
Blink Reformat4c46d092018-04-07 15:32:37491 }
492 }
493
494 /**
495 * @param {string} title
496 * @param {number} count
497 * @param {string} headersText
498 * @param {!UI.TreeElement} headersTreeElement
499 */
500 _refreshHeadersText(title, count, headersText, headersTreeElement) {
501 this._populateTreeElementWithSourceText(headersTreeElement, headersText);
502 this._refreshHeadersTitle(title, headersTreeElement, count);
503 }
504
505 _refreshRemoteAddress() {
506 const remoteAddress = this._request.remoteAddress();
507 const treeElement = this._remoteAddressItem;
508 treeElement.hidden = !remoteAddress;
509 if (remoteAddress)
510 treeElement.title = this._formatHeader(Common.UIString('Remote Address'), remoteAddress);
511 }
512
513 _refreshReferrerPolicy() {
514 const referrerPolicy = this._request.referrerPolicy();
515 const treeElement = this._referrerPolicyItem;
516 treeElement.hidden = !referrerPolicy;
517 if (referrerPolicy)
518 treeElement.title = this._formatHeader(Common.UIString('Referrer Policy'), referrerPolicy);
519 }
520
521 /**
522 * @param {!Event} event
523 */
524 _toggleRequestHeadersText(event) {
525 this._showRequestHeadersText = !this._showRequestHeadersText;
526 this._refreshRequestHeaders();
527 event.consume();
528 }
529
530 /**
531 * @param {!Event} event
532 */
533 _toggleResponseHeadersText(event) {
534 this._showResponseHeadersText = !this._showResponseHeadersText;
535 this._refreshResponseHeaders();
536 event.consume();
537 }
538
539 /**
540 * @param {string} title
541 * @return {!Element}
542 */
543 _createToggleButton(title) {
544 const button = createElementWithClass('span', 'header-toggle');
545 button.textContent = title;
546 return button;
547 }
548
549 /**
550 * @param {boolean} isHeadersTextShown
551 * @return {!Element}
552 */
553 _createHeadersToggleButton(isHeadersTextShown) {
554 const toggleTitle = isHeadersTextShown ? Common.UIString('view parsed') : Common.UIString('view source');
555 return this._createToggleButton(toggleTitle);
556 }
557
558 _clearHighlight() {
559 if (this._highlightedElement)
560 this._highlightedElement.listItemElement.classList.remove('header-highlight');
561 this._highlightedElement = null;
562 }
563
564
565 /**
566 * @param {?UI.TreeElement} category
567 * @param {string} name
568 */
569 _revealAndHighlight(category, name) {
570 this._clearHighlight();
571 for (const element of category.children()) {
572 if (element[Network.RequestHeadersView._headerNameSymbol] !== name)
573 continue;
574 this._highlightedElement = element;
575 element.reveal();
576 element.listItemElement.classList.add('header-highlight');
577 return;
578 }
579 }
580
581 /**
582 * @param {string} header
583 */
584 revealRequestHeader(header) {
585 this._revealAndHighlight(this._requestHeadersCategory, header);
586 }
587
588 /**
589 * @param {string} header
590 */
591 revealResponseHeader(header) {
592 this._revealAndHighlight(this._responseHeadersCategory, header);
593 }
594};
595
596Network.RequestHeadersView._headerNameSymbol = Symbol('HeaderName');
597Network.RequestHeadersView._viewSourceSymbol = Symbol('ViewSource');
598
599/**
600 * @unrestricted
601 */
602Network.RequestHeadersView.Category = class extends UI.TreeElement {
603 /**
604 * @param {!UI.TreeOutline} root
605 * @param {string} name
606 * @param {string=} title
607 */
608 constructor(root, name, title) {
609 super(title || '', true);
610 this.selectable = false;
611 this.toggleOnClick = true;
612 this.hidden = true;
613 this._expandedSetting = Common.settings.createSetting('request-info-' + name + '-category-expanded', true);
614 this.expanded = this._expandedSetting.get();
615 root.appendChild(this);
616 }
617
618 /**
619 * @return {!UI.TreeElement}
620 */
621 createLeaf() {
622 const leaf = new UI.TreeElement();
623 leaf.selectable = false;
624 this.appendChild(leaf);
625 return leaf;
626 }
627
628 /**
629 * @override
630 */
631 onexpand() {
632 this._expandedSetting.set(true);
633 }
634
635 /**
636 * @override
637 */
638 oncollapse() {
639 this._expandedSetting.set(false);
640 }
641};