RequestListContextMenu.js (26490B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 L10N, 9 } = require("resource://devtools/client/netmonitor/src/utils/l10n.js"); 10 const { 11 formDataURI, 12 getUrlQuery, 13 getUrlBaseName, 14 parseQueryString, 15 getRequestHeadersRawText, 16 } = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); 17 const { 18 hasMatchingBlockingRequestPattern, 19 } = require("resource://devtools/client/netmonitor/src/utils/request-blocking.js"); 20 21 loader.lazyRequireGetter( 22 this, 23 "Curl", 24 "resource://devtools/client/shared/curl.js", 25 true 26 ); 27 loader.lazyRequireGetter( 28 this, 29 "saveAs", 30 "resource://devtools/shared/DevToolsUtils.js", 31 true 32 ); 33 loader.lazyRequireGetter( 34 this, 35 "PowerShell", 36 "resource://devtools/client/netmonitor/src/utils/powershell.js", 37 true 38 ); 39 loader.lazyRequireGetter( 40 this, 41 "copyString", 42 "resource://devtools/shared/platform/clipboard.js", 43 true 44 ); 45 loader.lazyRequireGetter( 46 this, 47 "showMenu", 48 "resource://devtools/client/shared/components/menu/utils.js", 49 true 50 ); 51 loader.lazyRequireGetter( 52 this, 53 "HarMenuUtils", 54 "resource://devtools/client/netmonitor/src/har/har-menu-utils.js", 55 true 56 ); 57 loader.lazyRequireGetter( 58 this, 59 ["setNetworkOverride", "removeNetworkOverride"], 60 "resource://devtools/client/framework/actions/index.js", 61 true 62 ); 63 loader.lazyRequireGetter( 64 this, 65 "getOverriddenUrl", 66 "resource://devtools/client/netmonitor/src/selectors/index.js", 67 true 68 ); 69 loader.lazyRequireGetter( 70 this, 71 "openRequestInTab", 72 "resource://devtools/client/netmonitor/src/utils/firefox/open-request-in-tab.js", 73 true 74 ); 75 76 const { OS } = Services.appinfo; 77 78 class RequestListContextMenu { 79 constructor(props) { 80 this.props = props; 81 } 82 83 createCopySubMenu(clickedRequest, requests) { 84 const { connector } = this.props; 85 86 const { 87 id, 88 formDataSections, 89 method, 90 mimeType, 91 httpVersion, 92 requestHeaders, 93 requestHeadersAvailable, 94 requestPostData, 95 requestPostDataAvailable, 96 responseHeaders, 97 responseHeadersAvailable, 98 responseContent, 99 responseContentAvailable, 100 url, 101 } = clickedRequest; 102 103 const copySubMenu = []; 104 105 copySubMenu.push({ 106 id: "request-list-context-copy-url", 107 label: L10N.getStr("netmonitor.context.copyUrl"), 108 accesskey: L10N.getStr("netmonitor.context.copyUrl.accesskey"), 109 visible: !!clickedRequest, 110 click: () => this.copyUrl(url), 111 }); 112 113 copySubMenu.push({ 114 id: "request-list-context-copy-url-params", 115 label: L10N.getStr("netmonitor.context.copyUrlParams"), 116 accesskey: L10N.getStr("netmonitor.context.copyUrlParams.accesskey"), 117 visible: !!(clickedRequest && getUrlQuery(url)), 118 click: () => this.copyUrlParams(url), 119 }); 120 121 copySubMenu.push({ 122 id: "request-list-context-copy-post-data", 123 label: L10N.getFormatStr("netmonitor.context.copyRequestData", method), 124 accesskey: L10N.getStr("netmonitor.context.copyRequestData.accesskey"), 125 // Menu item will be visible even if data hasn't arrived, so we need to check 126 // *Available property and then fetch data lazily once user triggers the action. 127 visible: !!( 128 clickedRequest && 129 (requestPostDataAvailable || requestPostData) 130 ), 131 click: () => this.copyPostData(id, formDataSections, requestPostData), 132 }); 133 134 if (OS === "WINNT") { 135 copySubMenu.push({ 136 id: "request-list-context-copy-as-curl-win", 137 label: L10N.getFormatStr( 138 "netmonitor.context.copyAsCurl.win", 139 L10N.getStr("netmonitor.context.copyAsCurl") 140 ), 141 accesskey: L10N.getStr("netmonitor.context.copyAsCurl.win.accesskey"), 142 // Menu item will be visible even if data hasn't arrived, so we need to check 143 // *Available property and then fetch data lazily once user triggers the action. 144 visible: !!clickedRequest, 145 click: () => 146 this.copyAsCurl( 147 id, 148 url, 149 method, 150 httpVersion, 151 requestHeaders, 152 requestPostData, 153 responseHeaders, 154 "WINNT" 155 ), 156 }); 157 158 copySubMenu.push({ 159 id: "request-list-context-copy-as-curl-posix", 160 label: L10N.getFormatStr( 161 "netmonitor.context.copyAsCurl.posix", 162 L10N.getStr("netmonitor.context.copyAsCurl") 163 ), 164 accesskey: L10N.getStr("netmonitor.context.copyAsCurl.posix.accesskey"), 165 // Menu item will be visible even if data hasn't arrived, so we need to check 166 // *Available property and then fetch data lazily once user triggers the action. 167 visible: !!clickedRequest, 168 click: () => 169 this.copyAsCurl( 170 id, 171 url, 172 method, 173 httpVersion, 174 requestHeaders, 175 requestPostData, 176 responseHeaders, 177 "Linux" 178 ), 179 }); 180 } else { 181 copySubMenu.push({ 182 id: "request-list-context-copy-as-curl", 183 label: L10N.getStr("netmonitor.context.copyAsCurl"), 184 accesskey: L10N.getStr("netmonitor.context.copyAsCurl.accesskey"), 185 // Menu item will be visible even if data hasn't arrived, so we need to check 186 // *Available property and then fetch data lazily once user triggers the action. 187 visible: !!clickedRequest, 188 click: () => 189 this.copyAsCurl( 190 id, 191 url, 192 method, 193 httpVersion, 194 requestHeaders, 195 requestPostData, 196 responseHeaders 197 ), 198 }); 199 } 200 201 copySubMenu.push({ 202 id: "request-list-context-copy-as-powershell", 203 label: L10N.getStr("netmonitor.context.copyAsPowerShell"), 204 accesskey: L10N.getStr("netmonitor.context.copyAsPowerShell.accesskey"), 205 // Menu item will be visible even if data hasn't arrived, so we need to check 206 // *Available property and then fetch data lazily once user triggers the action. 207 visible: !!clickedRequest, 208 click: () => this.copyAsPowerShell(clickedRequest), 209 }); 210 211 copySubMenu.push({ 212 id: "request-list-context-copy-as-fetch", 213 label: L10N.getStr("netmonitor.context.copyAsFetch"), 214 accesskey: L10N.getStr("netmonitor.context.copyAsFetch.accesskey"), 215 visible: !!clickedRequest, 216 click: () => 217 this.copyAsFetch(id, url, method, requestHeaders, requestPostData), 218 }); 219 220 copySubMenu.push({ 221 type: "separator", 222 visible: copySubMenu.slice(0, 4).some(subMenu => subMenu.visible), 223 }); 224 225 copySubMenu.push({ 226 id: "request-list-context-copy-request-headers", 227 label: L10N.getStr("netmonitor.context.copyRequestHeaders"), 228 accesskey: L10N.getStr("netmonitor.context.copyRequestHeaders.accesskey"), 229 // Menu item will be visible even if data hasn't arrived, so we need to check 230 // *Available property and then fetch data lazily once user triggers the action. 231 visible: !!( 232 clickedRequest && 233 (requestHeadersAvailable || requestHeaders) 234 ), 235 click: () => this.copyRequestHeaders(id, clickedRequest), 236 }); 237 238 copySubMenu.push({ 239 id: "response-list-context-copy-response-headers", 240 label: L10N.getStr("netmonitor.context.copyResponseHeaders"), 241 accesskey: L10N.getStr( 242 "netmonitor.context.copyResponseHeaders.accesskey" 243 ), 244 // Menu item will be visible even if data hasn't arrived, so we need to check 245 // *Available property and then fetch data lazily once user triggers the action. 246 visible: !!( 247 clickedRequest && 248 (responseHeadersAvailable || responseHeaders) 249 ), 250 click: () => this.copyResponseHeaders(id, responseHeaders), 251 }); 252 253 copySubMenu.push({ 254 id: "request-list-context-copy-response", 255 label: L10N.getStr("netmonitor.context.copyResponse"), 256 accesskey: L10N.getStr("netmonitor.context.copyResponse.accesskey"), 257 // Menu item will be visible even if data hasn't arrived, so we need to check 258 // *Available property and then fetch data lazily once user triggers the action. 259 visible: !!( 260 clickedRequest && 261 (responseContentAvailable || responseContent) 262 ), 263 click: () => this.copyResponse(id, responseContent), 264 }); 265 266 copySubMenu.push({ 267 id: "request-list-context-copy-image-as-data-uri", 268 label: L10N.getStr("netmonitor.context.copyImageAsDataUri"), 269 accesskey: L10N.getStr("netmonitor.context.copyImageAsDataUri.accesskey"), 270 visible: !!( 271 clickedRequest && 272 (responseContentAvailable || responseContent) && 273 mimeType && 274 mimeType.includes("image/") 275 ), 276 click: () => this.copyImageAsDataUri(id, mimeType, responseContent), 277 }); 278 279 copySubMenu.push({ 280 type: "separator", 281 visible: copySubMenu.slice(5, 9).some(subMenu => subMenu.visible), 282 }); 283 284 copySubMenu.push({ 285 id: "request-list-context-copy-all-as-har", 286 label: L10N.getStr("netmonitor.context.copyAllAsHar"), 287 accesskey: L10N.getStr("netmonitor.context.copyAllAsHar.accesskey"), 288 visible: !!requests.length, 289 click: () => HarMenuUtils.copyAllAsHar(requests, connector), 290 }); 291 292 return copySubMenu; 293 } 294 295 createMenu(clickedRequest, requests, blockedUrls) { 296 const { 297 connector, 298 cloneRequest, 299 openDetailsPanelTab, 300 openHTTPCustomRequestTab, 301 closeHTTPCustomRequestTab, 302 sendCustomRequest, 303 sendHTTPCustomRequest, 304 openStatistics, 305 openRequestBlockingAndAddUrl, 306 openRequestBlockingAndDisableUrls, 307 removeBlockedUrl, 308 } = this.props; 309 310 const { 311 id, 312 isCustom, 313 method, 314 cause, 315 isEventStream, 316 mimeType, 317 requestHeaders, 318 requestPostData, 319 responseContent, 320 responseContentAvailable, 321 url, 322 } = clickedRequest; 323 324 const toolbox = this.props.connector.getToolbox(); 325 const isOverridden = !!getOverriddenUrl(toolbox.store.getState(), url); 326 const isLocalTab = toolbox.commands.descriptorFront.isLocalTab; 327 328 const copySubMenu = this.createCopySubMenu(clickedRequest, requests); 329 const newEditAndResendPref = Services.prefs.getBoolPref( 330 "devtools.netmonitor.features.newEditAndResend" 331 ); 332 333 return [ 334 { 335 label: L10N.getStr("netmonitor.context.copyValue"), 336 accesskey: L10N.getStr("netmonitor.context.copyValue.accesskey"), 337 visible: true, 338 submenu: copySubMenu, 339 }, 340 { 341 id: "request-list-context-save-as-har", 342 label: L10N.getStr("netmonitor.context.saveAsHar"), 343 accesskey: L10N.getStr("netmonitor.context.saveAsHar.accesskey"), 344 visible: !!clickedRequest, 345 click: () => HarMenuUtils.saveAsHar(clickedRequest, connector), 346 }, 347 { 348 id: "request-list-context-save-all-as-har", 349 label: L10N.getStr("netmonitor.context.saveAllAsHar"), 350 accesskey: L10N.getStr("netmonitor.context.saveAllAsHar.accesskey"), 351 visible: !!requests.length, 352 click: () => HarMenuUtils.saveAllAsHar(requests, connector), 353 }, 354 { 355 id: "request-list-context-save-response-as", 356 label: L10N.getStr("netmonitor.context.saveResponseAs"), 357 accesskey: L10N.getStr("netmonitor.context.saveResponseAs.accesskey"), 358 visible: !!( 359 (responseContentAvailable || responseContent) && 360 mimeType && 361 // Websockets and server-sent events don't have a real 'response' for us to save 362 cause.type !== "websocket" && 363 !isEventStream 364 ), 365 click: () => this.saveResponseAs(id, url, responseContent), 366 }, 367 { 368 type: "separator", 369 visible: copySubMenu.slice(10, 14).some(subMenu => subMenu.visible), 370 }, 371 { 372 id: "request-list-context-resend-only", 373 label: L10N.getStr("netmonitor.context.resend.label"), 374 accesskey: L10N.getStr("netmonitor.context.resend.accesskey"), 375 visible: !isCustom, 376 click: () => { 377 if (!newEditAndResendPref) { 378 cloneRequest(id); 379 sendCustomRequest(); 380 } else { 381 sendHTTPCustomRequest(clickedRequest); 382 } 383 }, 384 }, 385 386 { 387 id: "request-list-context-edit-resend", 388 label: L10N.getStr("netmonitor.context.editAndResend"), 389 accesskey: L10N.getStr("netmonitor.context.editAndResend.accesskey"), 390 visible: !isCustom, 391 click: () => { 392 this.fetchRequestHeaders(id).then(() => { 393 if (!newEditAndResendPref) { 394 cloneRequest(id); 395 openDetailsPanelTab(); 396 } else { 397 closeHTTPCustomRequestTab(); 398 openHTTPCustomRequestTab(); 399 } 400 }); 401 }, 402 }, 403 // Request blocking 404 { 405 id: "request-list-context-block-url", 406 label: L10N.getStr("netmonitor.context.blockURL"), 407 visible: !hasMatchingBlockingRequestPattern( 408 blockedUrls, 409 clickedRequest.url 410 ), 411 click: () => { 412 openRequestBlockingAndAddUrl(clickedRequest.url); 413 }, 414 }, 415 { 416 id: "request-list-context-unblock-url", 417 label: L10N.getStr("netmonitor.context.unblockURL"), 418 visible: hasMatchingBlockingRequestPattern( 419 blockedUrls, 420 clickedRequest.url 421 ), 422 click: () => { 423 if ( 424 blockedUrls.find(blockedUrl => blockedUrl === clickedRequest.url) 425 ) { 426 removeBlockedUrl(clickedRequest.url); 427 } else { 428 openRequestBlockingAndDisableUrls(clickedRequest.url); 429 } 430 }, 431 }, 432 // Request overrides 433 { 434 id: "request-list-context-set-override", 435 label: L10N.getStr("netmonitor.context.setOverride"), 436 accesskey: L10N.getStr("netmonitor.context.setOverride.accesskey"), 437 visible: 438 // Network overrides are disabled for remote debugging (bug 1881441). 439 isLocalTab && 440 !isOverridden && 441 (responseContentAvailable || responseContent), 442 click: async () => { 443 const content = await this.getResponseContent(id, responseContent); 444 toolbox.store.dispatch( 445 setNetworkOverride(toolbox.commands, url, content, window) 446 ); 447 }, 448 }, 449 { 450 id: "request-list-context-remove-override", 451 label: L10N.getStr("netmonitor.context.removeOverride"), 452 accesskey: L10N.getStr("netmonitor.context.removeOverride.accesskey"), 453 // Network overrides are disabled for remote debugging (bug 1881441). 454 visible: isLocalTab && isOverridden, 455 click: () => 456 toolbox.store.dispatch(removeNetworkOverride(toolbox.commands, url)), 457 }, 458 { 459 type: "separator", 460 visible: copySubMenu.slice(15, 16).some(subMenu => subMenu.visible), 461 }, 462 { 463 id: "request-list-context-newtab", 464 label: L10N.getStr("netmonitor.context.newTab"), 465 accesskey: L10N.getStr("netmonitor.context.newTab.accesskey"), 466 visible: !!clickedRequest, 467 click: () => 468 this.openRequestInTab(id, url, requestHeaders, requestPostData), 469 }, 470 { 471 id: "request-list-context-open-in-debugger", 472 label: L10N.getStr("netmonitor.context.openInDebugger"), 473 accesskey: L10N.getStr("netmonitor.context.openInDebugger.accesskey"), 474 visible: !!( 475 clickedRequest && 476 mimeType && 477 mimeType.includes("javascript") 478 ), 479 click: () => this.openInDebugger(url), 480 }, 481 { 482 id: "request-list-context-open-in-style-editor", 483 label: L10N.getStr("netmonitor.context.openInStyleEditor"), 484 accesskey: L10N.getStr( 485 "netmonitor.context.openInStyleEditor.accesskey" 486 ), 487 visible: !!( 488 clickedRequest && 489 Services.prefs.getBoolPref("devtools.styleeditor.enabled") && 490 mimeType && 491 mimeType.includes("css") 492 ), 493 click: () => this.openInStyleEditor(url), 494 }, 495 { 496 id: "request-list-context-perf", 497 label: L10N.getStr("netmonitor.context.perfTools"), 498 accesskey: L10N.getStr("netmonitor.context.perfTools.accesskey"), 499 visible: !!requests.length, 500 click: () => openStatistics(true), 501 }, 502 { 503 type: "separator", 504 }, 505 { 506 id: "request-list-context-use-as-fetch", 507 label: L10N.getStr("netmonitor.context.useAsFetch"), 508 accesskey: L10N.getStr("netmonitor.context.useAsFetch.accesskey"), 509 visible: !!clickedRequest, 510 click: () => 511 this.useAsFetch(id, url, method, requestHeaders, requestPostData), 512 }, 513 ]; 514 } 515 516 /** 517 * Opens selected item in a new tab. 518 */ 519 async openRequestInTab(id, url, requestHeaders, requestPostData) { 520 requestHeaders = 521 requestHeaders || 522 (await this.props.connector.requestData(id, "requestHeaders")); 523 524 requestPostData = 525 requestPostData || 526 (await this.props.connector.requestData(id, "requestPostData")); 527 528 openRequestInTab(url, requestHeaders, requestPostData); 529 } 530 531 open(event, clickedRequest, displayedRequests, blockedUrls) { 532 const enabledBlockedUrls = blockedUrls 533 .map(({ enabled, url }) => (enabled ? url : null)) 534 .filter(Boolean); 535 536 const menu = this.createMenu( 537 clickedRequest, 538 displayedRequests, 539 enabledBlockedUrls 540 ); 541 542 showMenu(menu, { 543 screenX: event.screenX, 544 screenY: event.screenY, 545 }); 546 } 547 548 /** 549 * Opens selected item in the debugger 550 */ 551 openInDebugger(url) { 552 const toolbox = this.props.connector.getToolbox(); 553 toolbox.viewGeneratedSourceInDebugger(url); 554 } 555 556 /** 557 * Opens selected item in the style editor 558 */ 559 openInStyleEditor(url) { 560 const toolbox = this.props.connector.getToolbox(); 561 toolbox.viewGeneratedSourceInStyleEditor(url); 562 } 563 564 /** 565 * Copy the request url from the currently selected item. 566 */ 567 copyUrl(url) { 568 copyString(url); 569 } 570 571 /** 572 * Copy the request url query string parameters from the currently 573 * selected item. 574 */ 575 copyUrlParams(url) { 576 const params = getUrlQuery(url).split("&"); 577 copyString(params.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n")); 578 } 579 580 /** 581 * Copy the request form data parameters (or raw payload) from 582 * the currently selected item. 583 */ 584 async copyPostData(id, formDataSections, requestPostData) { 585 let params = []; 586 // Try to extract any form data parameters if formDataSections is already 587 // available, which is only true if RequestPanel has ever been mounted before. 588 if (formDataSections) { 589 formDataSections.forEach(section => { 590 const paramsArray = parseQueryString(section); 591 if (paramsArray) { 592 params = [...params, ...paramsArray]; 593 } 594 }); 595 } 596 597 let string = params 598 .map(param => param.name + (param.value ? "=" + param.value : "")) 599 .join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n"); 600 601 // Fall back to raw payload. 602 if (!string) { 603 requestPostData = 604 requestPostData || 605 (await this.props.connector.requestData(id, "requestPostData")); 606 607 string = requestPostData.postData.text; 608 if (Services.appinfo.OS !== "WINNT") { 609 string = string.replace(/\r/g, ""); 610 } 611 } 612 copyString(string); 613 } 614 615 /** 616 * Copy a cURL command from the currently selected item. 617 */ 618 async copyAsCurl( 619 id, 620 url, 621 method, 622 httpVersion, 623 requestHeaders, 624 requestPostData, 625 responseHeaders, 626 platform 627 ) { 628 requestHeaders = 629 requestHeaders || 630 (await this.props.connector.requestData(id, "requestHeaders")); 631 632 requestPostData = 633 requestPostData || 634 (await this.props.connector.requestData(id, "requestPostData")); 635 636 responseHeaders = 637 responseHeaders || 638 (await this.props.connector.requestData(id, "responseHeaders")); 639 640 // Create a sanitized object for the Curl command generator. 641 const data = { 642 url, 643 method, 644 headers: requestHeaders.headers, 645 responseHeaders: responseHeaders.headers, 646 httpVersion, 647 postDataText: requestPostData ? requestPostData.postData.text : "", 648 }; 649 copyString(Curl.generateCommand(data, platform)); 650 } 651 652 async copyAsPowerShell(request) { 653 let { id, url, method, requestHeaders, requestPostData, requestCookies } = 654 request; 655 656 requestHeaders = 657 requestHeaders || 658 (await this.props.connector.requestData(id, "requestHeaders")); 659 660 requestPostData = 661 requestPostData || 662 (await this.props.connector.requestData(id, "requestPostData")); 663 664 requestCookies = 665 requestCookies || 666 (await this.props.connector.requestData(id, "requestCookies")); 667 668 return copyString( 669 PowerShell.generateCommand( 670 url, 671 method, 672 requestHeaders.headers, 673 requestPostData.postData, 674 requestCookies.cookies || requestCookies 675 ) 676 ); 677 } 678 679 /** 680 * Generate fetch string 681 */ 682 async generateFetchString(id, url, method, requestHeaders, requestPostData) { 683 requestHeaders = 684 requestHeaders || 685 (await this.props.connector.requestData(id, "requestHeaders")); 686 687 requestPostData = 688 requestPostData || 689 (await this.props.connector.requestData(id, "requestPostData")); 690 691 // https://fetch.spec.whatwg.org/#forbidden-header-name 692 const forbiddenHeaders = { 693 "accept-charset": 1, 694 "accept-encoding": 1, 695 "access-control-request-headers": 1, 696 "access-control-request-method": 1, 697 connection: 1, 698 "content-length": 1, 699 cookie: 1, 700 cookie2: 1, 701 date: 1, 702 dnt: 1, 703 expect: 1, 704 host: 1, 705 "keep-alive": 1, 706 origin: 1, 707 referer: 1, 708 te: 1, 709 trailer: 1, 710 "transfer-encoding": 1, 711 upgrade: 1, 712 via: 1, 713 }; 714 const credentialHeaders = { cookie: 1, authorization: 1 }; 715 716 const headers = {}; 717 for (const { name, value } of requestHeaders.headers) { 718 if (!forbiddenHeaders[name.toLowerCase()]) { 719 headers[name] = value; 720 } 721 } 722 723 const referrerHeader = requestHeaders.headers.find( 724 ({ name }) => name.toLowerCase() === "referer" 725 ); 726 727 const referrerPolicy = requestHeaders.headers.find( 728 ({ name }) => name.toLowerCase() === "referrer-policy" 729 ); 730 731 const referrer = referrerHeader ? referrerHeader.value : undefined; 732 const credentials = requestHeaders.headers.some( 733 ({ name }) => credentialHeaders[name.toLowerCase()] 734 ) 735 ? "include" 736 : "omit"; 737 738 const fetchOptions = { 739 credentials, 740 headers, 741 referrer, 742 referrerPolicy, 743 body: requestPostData.postData.text, 744 method, 745 mode: "cors", 746 }; 747 748 const options = JSON.stringify(fetchOptions, null, 4); 749 const fetchString = `await fetch("${url}", ${options});`; 750 return fetchString; 751 } 752 753 /** 754 * Copy the currently selected item as fetch request. 755 */ 756 async copyAsFetch(id, url, method, requestHeaders, requestPostData) { 757 const fetchString = await this.generateFetchString( 758 id, 759 url, 760 method, 761 requestHeaders, 762 requestPostData 763 ); 764 copyString(fetchString); 765 } 766 767 /** 768 * Open split console and fill it with fetch command for selected item 769 */ 770 async useAsFetch(id, url, method, requestHeaders, requestPostData) { 771 const fetchString = await this.generateFetchString( 772 id, 773 url, 774 method, 775 requestHeaders, 776 requestPostData 777 ); 778 const toolbox = this.props.connector.getToolbox(); 779 await toolbox.openSplitConsole(); 780 const { hud } = await toolbox.getPanel("webconsole"); 781 hud.setInputValue(fetchString); 782 } 783 784 /** 785 * Copy the raw request headers from the currently selected item. 786 */ 787 async copyRequestHeaders( 788 id, 789 { method, httpVersion, requestHeaders, urlDetails } 790 ) { 791 requestHeaders = 792 requestHeaders || 793 (await this.props.connector.requestData(id, "requestHeaders")); 794 795 let rawHeaders = getRequestHeadersRawText( 796 method, 797 httpVersion, 798 requestHeaders, 799 urlDetails 800 ); 801 802 if (Services.appinfo.OS !== "WINNT") { 803 rawHeaders = rawHeaders.replace(/\r/g, ""); 804 } 805 copyString(rawHeaders); 806 } 807 808 /** 809 * Copy the raw response headers from the currently selected item. 810 */ 811 async copyResponseHeaders(id, responseHeaders) { 812 responseHeaders = 813 responseHeaders || 814 (await this.props.connector.requestData(id, "responseHeaders")); 815 816 let rawHeaders = responseHeaders.rawHeaders.trim(); 817 818 if (Services.appinfo.OS !== "WINNT") { 819 rawHeaders = rawHeaders.replace(/\r/g, ""); 820 } 821 copyString(rawHeaders); 822 } 823 824 /** 825 * Copy image as data uri. 826 */ 827 async copyImageAsDataUri(id, mimeType, responseContent) { 828 responseContent = 829 responseContent || 830 (await this.props.connector.requestData(id, "responseContent")); 831 832 const { encoding, text } = responseContent.content; 833 copyString(formDataURI(mimeType, encoding, text)); 834 } 835 836 async getResponseContent(id, responseContent) { 837 responseContent = 838 responseContent || 839 (await this.props.connector.requestData(id, "responseContent")); 840 841 const { encoding, text } = responseContent.content; 842 let data; 843 if (encoding === "base64") { 844 const decoded = atob(text); 845 data = new Uint8Array(decoded.length); 846 for (let i = 0; i < decoded.length; ++i) { 847 data[i] = decoded.charCodeAt(i); 848 } 849 } else { 850 data = new TextEncoder().encode(text); 851 } 852 return data; 853 } 854 855 /** 856 * Save response as. 857 */ 858 async saveResponseAs(id, url, responseContent) { 859 const fileName = getUrlBaseName(url); 860 const content = await this.getResponseContent(id, responseContent); 861 saveAs(window, content, fileName); 862 } 863 864 /** 865 * Copy response data as a string. 866 */ 867 async copyResponse(id, responseContent) { 868 responseContent = 869 responseContent || 870 (await this.props.connector.requestData(id, "responseContent")); 871 872 copyString(responseContent.content.text); 873 } 874 875 async fetchRequestHeaders(id) { 876 await this.props.connector.requestData(id, "requestHeaders"); 877 } 878 } 879 880 module.exports = RequestListContextMenu;