HeadersPanel.js (26354B)
1 /* eslint-disable complexity */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 "use strict"; 7 8 const { 9 Component, 10 createFactory, 11 } = require("resource://devtools/client/shared/vendor/react.mjs"); 12 const { 13 connect, 14 } = require("resource://devtools/client/shared/vendor/react-redux.js"); 15 const Actions = require("resource://devtools/client/netmonitor/src/actions/index.js"); 16 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 17 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 18 const { 19 getFormattedIPAndPort, 20 getFormattedSize, 21 getRequestPriorityAsText, 22 } = require("resource://devtools/client/netmonitor/src/utils/format-utils.js"); 23 const { 24 L10N, 25 } = require("resource://devtools/client/netmonitor/src/utils/l10n.js"); 26 const { 27 getHeadersURL, 28 getTrackingProtectionURL, 29 getHTTPStatusCodeURL, 30 } = require("resource://devtools/client/netmonitor/src/utils/doc-utils.js"); 31 const { 32 fetchNetworkUpdatePacket, 33 writeHeaderText, 34 getRequestHeadersRawText, 35 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); 36 const { 37 HeadersProvider, 38 HeaderList, 39 } = require("resource://devtools/client/netmonitor/src/utils/headers-provider.js"); 40 const { 41 FILTER_SEARCH_DELAY, 42 } = require("resource://devtools/client/netmonitor/src/constants.js"); 43 // Components 44 const PropertiesView = createFactory( 45 require("resource://devtools/client/netmonitor/src/components/request-details/PropertiesView.js") 46 ); 47 const SearchBox = createFactory( 48 require("resource://devtools/client/shared/components/SearchBox.js") 49 ); 50 const Accordion = createFactory( 51 require("resource://devtools/client/shared/components/Accordion.js") 52 ); 53 const UrlPreview = createFactory( 54 require("resource://devtools/client/netmonitor/src/components/previews/UrlPreview.js") 55 ); 56 const HeadersPanelContextMenu = require("resource://devtools/client/netmonitor/src/widgets/HeadersPanelContextMenu.js"); 57 const StatusCode = createFactory( 58 require("resource://devtools/client/netmonitor/src/components/StatusCode.js") 59 ); 60 61 loader.lazyGetter(this, "MDNLink", function () { 62 return createFactory( 63 require("resource://devtools/client/shared/components/MdnLink.js") 64 ); 65 }); 66 loader.lazyGetter(this, "Rep", function () { 67 return ChromeUtils.importESModule( 68 "resource://devtools/client/shared/components/reps/index.mjs" 69 ).REPS.Rep; 70 }); 71 loader.lazyGetter(this, "MODE", function () { 72 return ChromeUtils.importESModule( 73 "resource://devtools/client/shared/components/reps/index.mjs" 74 ).MODE; 75 }); 76 loader.lazyGetter(this, "TreeRow", function () { 77 return createFactory( 78 ChromeUtils.importESModule( 79 "resource://devtools/client/shared/components/tree/TreeRow.mjs", 80 { global: "current" } 81 ).default 82 ); 83 }); 84 loader.lazyRequireGetter( 85 this, 86 "showMenu", 87 "resource://devtools/client/shared/components/menu/utils.js", 88 true 89 ); 90 loader.lazyRequireGetter( 91 this, 92 "openContentLink", 93 "resource://devtools/client/shared/link.js", 94 true 95 ); 96 97 loader.lazyGetter(this, "HEADERS_PROXY_STATUS", function () { 98 return L10N.getStr("netmonitor.headers.proxyStatus"); 99 }); 100 101 loader.lazyGetter(this, "HEADERS_PROXY_VERSION", function () { 102 return L10N.getStr("netmonitor.headers.proxyVersion"); 103 }); 104 105 const { div, input, label, span, textarea, tr, td, button } = dom; 106 107 const RESEND = L10N.getStr("netmonitor.context.resend.label"); 108 const EDIT_AND_RESEND = L10N.getStr("netmonitor.summary.editAndResend"); 109 const RAW_HEADERS = L10N.getStr("netmonitor.headers.raw"); 110 const HEADERS_EMPTY_TEXT = L10N.getStr("headersEmptyText"); 111 const HEADERS_FILTER_TEXT = L10N.getStr("headersFilterText"); 112 const HEADERS_STATUS = L10N.getStr("netmonitor.headers.status"); 113 const HEADERS_EARLYHINT_STATUS = L10N.getStr( 114 "netmonitor.headers.earlyHintsStatus" 115 ); 116 const HEADERS_VERSION = L10N.getStr("netmonitor.headers.version"); 117 const HEADERS_TRANSFERRED = L10N.getStr("netmonitor.toolbar.transferred"); 118 const SUMMARY_STATUS_LEARN_MORE = L10N.getStr("netmonitor.summary.learnMore"); 119 const SUMMARY_ETP_LEARN_MORE = L10N.getStr( 120 "netmonitor.enhancedTrackingProtection.learnMore" 121 ); 122 const HEADERS_REFERRER = L10N.getStr("netmonitor.headers.referrerPolicy"); 123 const HEADERS_CONTENT_BLOCKING = L10N.getStr( 124 "netmonitor.headers.contentBlocking" 125 ); 126 const HEADERS_ETP = L10N.getStr( 127 "netmonitor.trackingResource.enhancedTrackingProtection" 128 ); 129 const HEADERS_PRIORITY = L10N.getStr("netmonitor.headers.requestPriority"); 130 const HEADERS_DNS = L10N.getStr("netmonitor.headers.dns"); 131 132 // Order is as displayed 133 const HEADERS_CONFIG = { 134 earlyHintsResponseHeaders: { 135 // Key for fetching the data from the backend 136 fetchKey: "earlyHintsResponseHeaders", 137 title: L10N.getStr("earlyHintsResponseHeaders"), 138 // Gets the content to be displayed when switched to the raw headers view 139 rawHeaderValue: ({ headerData }) => { 140 const preHeaderText = headerData.rawHeaders.split("\r\n")[0]; 141 return writeHeaderText(headerData.headers, preHeaderText).trim(); 142 }, 143 // Gets the full display text to be displayed in the header title bar(before the raw toggle button) 144 displayTitle: ({ headerData }) => { 145 const title = HEADERS_CONFIG.earlyHintsResponseHeaders.title; 146 if (headerData.headersSize) { 147 return `${title} (${getFormattedSize(headerData.headersSize, 3)})`; 148 } 149 const rawHeaderValue = 150 HEADERS_CONFIG.earlyHintsResponseHeaders.rawHeaderValue({ headerData }); 151 return `${title} (${getFormattedSize(rawHeaderValue.length, 3)})`; 152 }, 153 }, 154 responseHeaders: { 155 // Key for fetching the data from the backend 156 fetchKey: "responseHeaders", 157 title: L10N.getStr("responseHeaders"), 158 // Gets the content to be displayed when switched to the raw headers view 159 rawHeaderValue: ({ status, statusText, httpVersion, headerData }) => { 160 const preHeaderText = `${httpVersion} ${status} ${statusText}`; 161 return writeHeaderText(headerData.headers, preHeaderText).trim(); 162 }, 163 // Gets the full display text to be displayed in the header title bar(before the raw toggle button) 164 displayTitle: ({ status, statusText, httpVersion, headerData }) => { 165 const title = HEADERS_CONFIG.responseHeaders.title; 166 if (headerData.headersSize) { 167 return `${title} (${getFormattedSize(headerData.headersSize, 3)})`; 168 } 169 const rawHeaderValue = HEADERS_CONFIG.responseHeaders.rawHeaderValue({ 170 httpVersion, 171 status, 172 statusText, 173 headerData, 174 }); 175 return `${title} (${getFormattedSize(rawHeaderValue.length, 3)})`; 176 }, 177 }, 178 requestHeaders: { 179 // See comment above 180 fetchKey: "requestHeaders", 181 title: L10N.getStr("requestHeaders"), 182 // See comment above 183 rawHeaderValue: ({ method, httpVersion, headerData, urlDetails }) => { 184 return getRequestHeadersRawText( 185 method, 186 httpVersion, 187 headerData, 188 urlDetails 189 ); 190 }, 191 // See comment above 192 displayTitle: ({ method, httpVersion, headerData, urlDetails }) => { 193 const title = HEADERS_CONFIG.requestHeaders.title; 194 if (headerData.headersSize) { 195 return `${title} (${getFormattedSize(headerData.headersSize, 3)})`; 196 } 197 const rawHeaderValue = HEADERS_CONFIG.requestHeaders.rawHeaderValue({ 198 method, 199 httpVersion, 200 headerData, 201 urlDetails, 202 }); 203 return `${title} (${getFormattedSize(rawHeaderValue.length, 3)})`; 204 }, 205 }, 206 requestHeadersFromUploadStream: { 207 // See comment above 208 fetchKey: "requestPostData", 209 title: L10N.getStr("requestHeadersFromUpload"), 210 // See comment above 211 rawHeaderValue: ({ headerData, preHeaderText = "" }) => { 212 return writeHeaderText(headerData.headers, preHeaderText).trim(); 213 }, 214 // See comment above 215 displayTitle: ({ method, httpVersion, headerData, urlDetails }) => { 216 const title = HEADERS_CONFIG.requestHeadersFromUploadStream.title; 217 if (headerData.headersSize) { 218 return `${title} (${getFormattedSize(headerData.headersSize, 3)})`; 219 } 220 let preHeaderText = ""; 221 const hostHeader = headerData.headers.find(ele => ele.name === "Host"); 222 if (hostHeader) { 223 preHeaderText = `${method} ${ 224 urlDetails.url.split(hostHeader.value)[1] 225 } ${httpVersion}`; 226 } 227 228 const rawHeaderValue = 229 HEADERS_CONFIG.requestHeadersFromUploadStream.rawHeaderValue({ 230 headerData, 231 preHeaderText, 232 }); 233 return `${title} (${getFormattedSize(rawHeaderValue.length, 3)})`; 234 }, 235 }, 236 }; 237 238 const HEADERS_TO_FETCH = Object.values(HEADERS_CONFIG).map( 239 headers => headers.fetchKey 240 ); 241 /** 242 * Headers panel component 243 * Lists basic information about the request 244 * 245 * In http/2 all response headers are in small case. 246 * See: https://firefox-source-docs.mozilla.org/devtools-user/network_monitor/request_details/index.html#response-headers 247 * RFC: https://tools.ietf.org/html/rfc7540#section-8.1.2 248 */ 249 class HeadersPanel extends Component { 250 static get propTypes() { 251 return { 252 connector: PropTypes.object.isRequired, 253 cloneSelectedRequest: PropTypes.func.isRequired, 254 member: PropTypes.object, 255 request: PropTypes.object.isRequired, 256 renderValue: PropTypes.func, 257 openLink: PropTypes.func, 258 targetSearchResult: PropTypes.object, 259 openRequestBlockingAndAddUrl: PropTypes.func.isRequired, 260 openHTTPCustomRequestTab: PropTypes.func.isRequired, 261 cloneRequest: PropTypes.func, 262 sendCustomRequest: PropTypes.func, 263 shouldExpandPreview: PropTypes.bool, 264 setHeadersUrlPreviewExpanded: PropTypes.func, 265 }; 266 } 267 268 constructor(props) { 269 super(props); 270 271 this.state = { 272 openedRawHeaders: new Set(), 273 lastToggledRawHeader: "", 274 filterText: null, 275 }; 276 277 this.getProperties = this.getProperties.bind(this); 278 this.getTargetHeaderPath = this.getTargetHeaderPath.bind(this); 279 this.toggleRawHeader = this.toggleRawHeader.bind(this); 280 this.renderSummary = this.renderSummary.bind(this); 281 this.renderRow = this.renderRow.bind(this); 282 this.renderValue = this.renderValue.bind(this); 283 this.renderRawHeadersBtn = this.renderRawHeadersBtn.bind(this); 284 this.onShowResendMenu = this.onShowResendMenu.bind(this); 285 this.onShowHeadersContextMenu = this.onShowHeadersContextMenu.bind(this); 286 } 287 288 componentDidMount() { 289 const { request, connector } = this.props; 290 fetchNetworkUpdatePacket(connector.requestData, request, HEADERS_TO_FETCH); 291 } 292 293 // FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507 294 UNSAFE_componentWillReceiveProps(nextProps) { 295 const { request, connector } = nextProps; 296 fetchNetworkUpdatePacket(connector.requestData, request, HEADERS_TO_FETCH); 297 } 298 299 // The title to be display in the heading 300 getHeadersDisplayTitle(headerKey) { 301 const headerData = this.props.request[headerKey]; 302 if (!headerData?.headers.length) { 303 return ""; 304 } 305 306 return HEADERS_CONFIG[headerKey].displayTitle({ 307 ...this.props.request, 308 headerData, 309 }); 310 } 311 312 getProperties(headerKey) { 313 let propertiesResult; 314 const headerData = this.props.request[headerKey]; 315 if (headerData?.headers.length) { 316 propertiesResult = { 317 [headerKey]: this.state.openedRawHeaders.has(headerKey) 318 ? { RAW_HEADERS_ID: headerData.rawHeaders } 319 : new HeaderList(headerData.headers), 320 }; 321 } 322 return propertiesResult; 323 } 324 // Toggles the raw headers view on / off 325 toggleRawHeader(headerKey) { 326 const newOpenedRawHeaders = new Set([...this.state.openedRawHeaders]); 327 if (newOpenedRawHeaders.has(headerKey)) { 328 newOpenedRawHeaders.delete(headerKey); 329 } else { 330 newOpenedRawHeaders.add(headerKey); 331 } 332 this.setState({ 333 openedRawHeaders: newOpenedRawHeaders, 334 lastToggledRawHeader: headerKey, 335 }); 336 } 337 338 /** 339 * Renders the top part of the headers detail panel - Summary. 340 */ 341 renderSummary(summaryLabel, value, summaryClass = "") { 342 return div( 343 { 344 key: summaryLabel, 345 className: "tabpanel-summary-container headers-summary " + summaryClass, 346 }, 347 span( 348 { className: "tabpanel-summary-label headers-summary-label" }, 349 summaryLabel 350 ), 351 span({ className: "tabpanel-summary-value" }, value) 352 ); 353 } 354 355 /** 356 * Get path for target header if it's set. It's used to select 357 * the header automatically within the tree of headers. 358 * Note that the target header is set by the Search panel. 359 */ 360 getTargetHeaderPath(searchResult) { 361 if (!searchResult || !(searchResult.type in HEADERS_CONFIG)) { 362 return null; 363 } 364 // Using `HeaderList` ensures that we'll get the same 365 // header index as it's used in the tree. 366 const headerData = this.props.request[searchResult.type]; 367 return ( 368 "/" + 369 searchResult.type + 370 "/" + 371 new HeaderList(headerData.headers).headers.findIndex( 372 header => header.name == searchResult.label 373 ) 374 ); 375 } 376 377 /** 378 * Custom rendering method passed to PropertiesView. It's responsible 379 * for rendering <textarea> element with raw headers data. 380 */ 381 renderRow(props) { 382 const { level, path } = props.member; 383 384 if (path.includes("RAW_HEADERS_ID")) { 385 const headerKey = path.split("/")[1]; 386 387 const value = HEADERS_CONFIG[headerKey].rawHeaderValue({ 388 ...this.props.request, 389 headerData: this.props.request[headerKey], 390 }); 391 392 let rows; 393 if (value) { 394 const match = value.match(/\n/g); 395 rows = match !== null ? match.length : 0; 396 // Need to add 1 for the horizontal scrollbar 397 // not to cover the last row of raw data 398 rows = rows + 1; 399 } 400 401 return tr( 402 { 403 key: path, 404 role: "treeitem", 405 className: "raw-headers-container", 406 onClick: event => event.stopPropagation(), 407 }, 408 td( 409 { 410 colSpan: 2, 411 }, 412 textarea({ 413 className: "raw-headers", 414 rows, 415 value, 416 readOnly: true, 417 }) 418 ) 419 ); 420 } 421 422 if (level !== 1) { 423 return null; 424 } 425 426 return TreeRow(props); 427 } 428 429 renderRawHeadersBtn(headerKey) { 430 return [ 431 label( 432 { 433 key: `${headerKey}RawHeadersBtn`, 434 className: "raw-headers-toggle", 435 onClick: event => event.stopPropagation(), 436 onKeyDown: event => event.stopPropagation(), 437 }, 438 span({ className: "headers-summary-label" }, RAW_HEADERS), 439 span( 440 { className: "raw-headers-toggle-input" }, 441 input({ 442 id: `raw-${headerKey}-checkbox`, 443 checked: this.state.openedRawHeaders.has(headerKey), 444 className: "devtools-checkbox-toggle", 445 onChange: () => this.toggleRawHeader(headerKey), 446 type: "checkbox", 447 }) 448 ) 449 ), 450 ]; 451 } 452 453 renderValue(props) { 454 const { member, value } = props; 455 456 if (typeof value !== "string") { 457 return null; 458 } 459 460 const headerDocURL = getHeadersURL(member.name); 461 462 return div( 463 { className: "treeValueCellDivider" }, 464 Rep( 465 Object.assign(props, { 466 // FIXME: A workaround for the issue in StringRep 467 // Force StringRep to crop the text everytime 468 member: Object.assign({}, member, { open: false }), 469 mode: MODE.TINY, 470 noGrip: true, 471 openLink: openContentLink, 472 }) 473 ), 474 headerDocURL ? MDNLink({ url: headerDocURL }) : null 475 ); 476 } 477 478 getShouldOpen(headerKey, filterText, targetSearchResult) { 479 return (item, opened) => { 480 // If closed, open panel for these reasons 481 // 1.The raw header is switched on or off 482 // 2.The filter text is set 483 // 3.The search text is set 484 if ( 485 (!opened && this.state.lastToggledRawHeader === headerKey) || 486 (!opened && filterText) || 487 (!opened && targetSearchResult) 488 ) { 489 return true; 490 } 491 return !!opened; 492 }; 493 } 494 495 onShowResendMenu(event) { 496 const { 497 request: { id }, 498 cloneSelectedRequest, 499 cloneRequest, 500 sendCustomRequest, 501 } = this.props; 502 const menuItems = [ 503 { 504 label: RESEND, 505 type: "button", 506 click: () => { 507 cloneRequest(id); 508 sendCustomRequest(); 509 }, 510 }, 511 { 512 label: EDIT_AND_RESEND, 513 type: "button", 514 click: evt => { 515 cloneSelectedRequest(evt); 516 }, 517 }, 518 ]; 519 520 showMenu(menuItems, { button: event.target }); 521 } 522 523 onShowHeadersContextMenu(event) { 524 if (!this.contextMenu) { 525 this.contextMenu = new HeadersPanelContextMenu(); 526 } 527 this.contextMenu.open(event, window.getSelection()); 528 } 529 530 render() { 531 const { 532 targetSearchResult, 533 request: { 534 fromCache, 535 fromServiceWorker, 536 httpVersion, 537 method, 538 remoteAddress, 539 remotePort, 540 status, 541 statusText, 542 urlDetails, 543 referrerPolicy, 544 priority, 545 isThirdPartyTrackingResource, 546 contentSize, 547 transferredSize, 548 isResolvedByTRR, 549 proxyHttpVersion, 550 proxyStatus, 551 proxyStatusText, 552 earlyHintsStatus, 553 }, 554 openRequestBlockingAndAddUrl, 555 openHTTPCustomRequestTab, 556 shouldExpandPreview, 557 setHeadersUrlPreviewExpanded, 558 } = this.props; 559 560 const headersDataExists = Object.keys(HEADERS_CONFIG).some( 561 headerKey => this.props.request[headerKey]?.headers.length 562 ); 563 564 if (!headersDataExists) { 565 return div({ className: "empty-notice" }, HEADERS_EMPTY_TEXT); 566 } 567 568 const items = []; 569 570 for (const headerKey in HEADERS_CONFIG) { 571 if (this.props.request[headerKey]?.headers.length) { 572 const { filterText } = this.state; 573 items.push({ 574 component: PropertiesView, 575 componentProps: { 576 object: this.getProperties(headerKey), 577 filterText, 578 targetSearchResult, 579 renderRow: this.renderRow, 580 renderValue: this.renderValue, 581 provider: HeadersProvider, 582 selectPath: this.getTargetHeaderPath, 583 defaultSelectFirstNode: false, 584 enableInput: false, 585 useQuotes: false, 586 }, 587 header: this.getHeadersDisplayTitle(headerKey), 588 buttons: this.renderRawHeadersBtn(headerKey), 589 id: headerKey, 590 opened: true, 591 shouldOpen: this.getShouldOpen( 592 headerKey, 593 filterText, 594 targetSearchResult 595 ), 596 }); 597 } 598 } 599 600 const sizeText = L10N.getFormatStrWithNumbers( 601 "netmonitor.headers.sizeDetails", 602 getFormattedSize(transferredSize), 603 getFormattedSize(contentSize) 604 ); 605 606 const summarySize = this.renderSummary(HEADERS_TRANSFERRED, sizeText); 607 608 let summaryEarlyStatus; 609 if (earlyHintsStatus) { 610 summaryEarlyStatus = div( 611 { 612 key: "headers-summary", 613 className: 614 "tabpanel-summary-container headers-summary headers-earlyhint-status", 615 }, 616 span( 617 { 618 className: "tabpanel-summary-label headers-summary-label", 619 }, 620 HEADERS_EARLYHINT_STATUS 621 ), 622 span( 623 { 624 className: "tabpanel-summary-value status", 625 "data-code": earlyHintsStatus, 626 }, 627 StatusCode({ 628 item: { 629 fromCache, 630 fromServiceWorker, 631 status: earlyHintsStatus, 632 statusText: "", 633 }, 634 }), 635 MDNLink({ 636 url: getHTTPStatusCodeURL(earlyHintsStatus), 637 title: SUMMARY_STATUS_LEARN_MORE, 638 }) 639 ) 640 ); 641 } 642 let summaryStatus; 643 if (status) { 644 summaryStatus = div( 645 { 646 key: "headers-summary", 647 className: "tabpanel-summary-container headers-summary", 648 }, 649 span( 650 { 651 className: "tabpanel-summary-label headers-summary-label", 652 }, 653 HEADERS_STATUS 654 ), 655 span( 656 { 657 className: "tabpanel-summary-value status", 658 "data-code": status, 659 }, 660 StatusCode({ 661 item: { fromCache, fromServiceWorker, status, statusText }, 662 }), 663 statusText, 664 MDNLink({ 665 url: getHTTPStatusCodeURL(status), 666 title: SUMMARY_STATUS_LEARN_MORE, 667 }) 668 ) 669 ); 670 } 671 672 let summaryProxyStatus; 673 if (proxyStatus) { 674 summaryProxyStatus = div( 675 { 676 key: "headers-summary ", 677 className: 678 "tabpanel-summary-container headers-summary headers-proxy-status", 679 }, 680 span( 681 { 682 className: "tabpanel-summary-label headers-summary-label", 683 }, 684 HEADERS_PROXY_STATUS 685 ), 686 span( 687 { 688 className: "tabpanel-summary-value status", 689 "data-code": proxyStatus, 690 }, 691 StatusCode({ 692 item: { 693 fromCache, 694 fromServiceWorker, 695 status: proxyStatus, 696 statusText: proxyStatusText, 697 }, 698 }), 699 proxyStatusText, 700 MDNLink({ 701 url: getHTTPStatusCodeURL(proxyStatus), 702 title: SUMMARY_STATUS_LEARN_MORE, 703 }) 704 ) 705 ); 706 } 707 708 let trackingProtectionStatus; 709 let trackingProtectionDetails = ""; 710 if (isThirdPartyTrackingResource) { 711 const trackingProtectionDocURL = getTrackingProtectionURL(); 712 713 trackingProtectionStatus = this.renderSummary( 714 HEADERS_CONTENT_BLOCKING, 715 div(null, span({ className: "tracking-resource" }), HEADERS_ETP) 716 ); 717 trackingProtectionDetails = this.renderSummary( 718 "", 719 div( 720 { 721 key: "tracking-protection", 722 className: "tracking-protection", 723 }, 724 L10N.getStr("netmonitor.trackingResource.tooltip"), 725 trackingProtectionDocURL 726 ? MDNLink({ 727 url: trackingProtectionDocURL, 728 title: SUMMARY_ETP_LEARN_MORE, 729 }) 730 : span({ className: "headers-summary learn-more-link" }) 731 ) 732 ); 733 } 734 735 const summaryVersion = httpVersion 736 ? this.renderSummary(HEADERS_VERSION, httpVersion) 737 : null; 738 739 const summaryProxyHttpVersion = proxyHttpVersion 740 ? this.renderSummary( 741 HEADERS_PROXY_VERSION, 742 proxyHttpVersion, 743 "headers-proxy-version" 744 ) 745 : null; 746 747 const summaryReferrerPolicy = referrerPolicy 748 ? this.renderSummary(HEADERS_REFERRER, referrerPolicy) 749 : null; 750 751 const summaryPriority = priority 752 ? this.renderSummary(HEADERS_PRIORITY, getRequestPriorityAsText(priority)) 753 : null; 754 755 const summaryDNS = this.renderSummary( 756 HEADERS_DNS, 757 L10N.getStr( 758 isResolvedByTRR 759 ? "netmonitor.headers.dns.overHttps" 760 : "netmonitor.headers.dns.basic" 761 ) 762 ); 763 764 const summaryItems = [ 765 summaryEarlyStatus, 766 summaryStatus, 767 summaryProxyStatus, 768 summaryVersion, 769 summaryProxyHttpVersion, 770 summarySize, 771 summaryReferrerPolicy, 772 summaryPriority, 773 summaryDNS, 774 trackingProtectionStatus, 775 trackingProtectionDetails, 776 ].filter(summaryItem => summaryItem !== null); 777 778 const newEditAndResendPref = Services.prefs.getBoolPref( 779 "devtools.netmonitor.features.newEditAndResend" 780 ); 781 782 return div( 783 { className: "headers-panel-container" }, 784 div( 785 { className: "devtools-toolbar devtools-input-toolbar" }, 786 SearchBox({ 787 delay: FILTER_SEARCH_DELAY, 788 type: "filter", 789 onChange: text => this.setState({ filterText: text }), 790 placeholder: HEADERS_FILTER_TEXT, 791 }), 792 span({ className: "devtools-separator" }), 793 button( 794 { 795 id: "block-button", 796 className: "devtools-button", 797 title: L10N.getStr("netmonitor.context.blockURL"), 798 onClick: () => openRequestBlockingAndAddUrl(urlDetails.url), 799 }, 800 L10N.getStr("netmonitor.headers.toolbar.block") 801 ), 802 span({ className: "devtools-separator" }), 803 button( 804 { 805 id: "edit-resend-button", 806 className: !newEditAndResendPref 807 ? "devtools-button devtools-dropdown-button" 808 : "devtools-button", 809 title: RESEND, 810 onClick: !newEditAndResendPref 811 ? this.onShowResendMenu 812 : () => { 813 openHTTPCustomRequestTab(); 814 }, 815 }, 816 span({ className: "title" }, RESEND) 817 ) 818 ), 819 div( 820 { className: "panel-container" }, 821 div( 822 { className: "headers-overview" }, 823 UrlPreview({ 824 url: urlDetails.url, 825 method, 826 address: remoteAddress 827 ? getFormattedIPAndPort(remoteAddress, remotePort) 828 : null, 829 shouldExpandPreview, 830 onTogglePreview: expanded => setHeadersUrlPreviewExpanded(expanded), 831 proxyStatus, 832 }), 833 div( 834 { 835 className: "summary", 836 onContextMenu: this.onShowHeadersContextMenu, 837 }, 838 summaryItems 839 ) 840 ), 841 Accordion({ items }) 842 ) 843 ); 844 } 845 } 846 847 module.exports = connect( 848 state => ({ 849 shouldExpandPreview: state.ui.shouldExpandHeadersUrlPreview, 850 }), 851 dispatch => ({ 852 setHeadersUrlPreviewExpanded: expanded => 853 dispatch(Actions.setHeadersUrlPreviewExpanded(expanded)), 854 openRequestBlockingAndAddUrl: url => 855 dispatch(Actions.openRequestBlockingAndAddUrl(url)), 856 openHTTPCustomRequestTab: () => 857 dispatch(Actions.openHTTPCustomRequest(true)), 858 cloneRequest: id => dispatch(Actions.cloneRequest(id)), 859 sendCustomRequest: () => dispatch(Actions.sendCustomRequest()), 860 }) 861 )(HeadersPanel);