| /* |
| * Copyright (C) 2010 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| import * as Common from '../../core/common/common.js'; |
| import * as i18n from '../../core/i18n/i18n.js'; |
| import * as SDK from '../../core/sdk/sdk.js'; |
| import * as NetworkForward from '../../panels/network/forward/forward.js'; |
| import * as UI from '../../ui/legacy/legacy.js'; |
| import * as NetworkComponents from './components/components.js'; |
| |
| import {EventSourceMessagesView} from './EventSourceMessagesView.js'; |
| import type {NetworkTimeCalculator} from './NetworkTimeCalculator.js'; |
| import {RequestCookiesView} from './RequestCookiesView.js'; |
| import {RequestHeadersView} from './RequestHeadersView.js'; |
| import {RequestPayloadView} from './RequestPayloadView.js'; |
| import {RequestInitiatorView} from './RequestInitiatorView.js'; |
| import {RequestPreviewView} from './RequestPreviewView.js'; |
| import {RequestResponseView} from './RequestResponseView.js'; |
| import {RequestTimingView} from './RequestTimingView.js'; |
| import {ResourceWebSocketFrameView} from './ResourceWebSocketFrameView.js'; |
| |
| const UIStrings = { |
| /** |
| *@description Text for network request headers |
| */ |
| headers: 'Headers', |
| /** |
| *@description Text in Network Item View of the Network panel |
| */ |
| payload: 'Payload', |
| /** |
| *@description Text in Network Item View of the Network panel |
| */ |
| messages: 'Messages', |
| /** |
| *@description Text in Network Item View of the Network panel |
| */ |
| websocketMessages: 'WebSocket messages', |
| /** |
| *@description Text in Network Item View of the Network panel |
| */ |
| eventstream: 'EventStream', |
| /** |
| *@description Text for previewing items |
| */ |
| preview: 'Preview', |
| /** |
| *@description Text in Network Item View of the Network panel |
| */ |
| responsePreview: 'Response preview', |
| /** |
| *@description Icon title in Network Item View of the Network panel |
| */ |
| signedexchangeError: 'SignedExchange error', |
| /** |
| *@description Title of a tab in the Network panel. A Network response refers to the act of acknowledging a |
| network request. Should not be confused with answer. |
| */ |
| response: 'Response', |
| /** |
| *@description Text in Network Item View of the Network panel |
| */ |
| rawResponseData: 'Raw response data', |
| /** |
| *@description Text for the initiator of something |
| */ |
| initiator: 'Initiator', |
| /** |
| * @description Tooltip for initiator view in Network panel. An initiator is a piece of code/entity |
| * in the code that initiated/started the network request, i.e. caused the network request. The 'call |
| * stack' is the location in the code where the initiation happened. |
| */ |
| requestInitiatorCallStack: 'Request initiator call stack', |
| /** |
| *@description Title of a tab in Network Item View of the Network panel. |
| *The tab displays the duration breakdown of a network request. |
| */ |
| timing: 'Timing', |
| /** |
| *@description Text in Network Item View of the Network panel |
| */ |
| requestAndResponseTimeline: 'Request and response timeline', |
| /** |
| *@description Label of a tab in the network panel |
| */ |
| trustTokens: 'Trust Tokens', |
| /** |
| *@description Title of the Trust token tab in the Network panel |
| */ |
| trustTokenOperationDetails: 'Trust Token operation details', |
| /** |
| *@description Text for web cookies |
| */ |
| cookies: 'Cookies', |
| /** |
| *@description Text in Network Item View of the Network panel |
| */ |
| requestAndResponseCookies: 'Request and response cookies', |
| }; |
| const str_ = i18n.i18n.registerUIStrings('panels/network/NetworkItemView.ts', UIStrings); |
| const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); |
| export class NetworkItemView extends UI.TabbedPane.TabbedPane { |
| private requestInternal: SDK.NetworkRequest.NetworkRequest; |
| private readonly resourceViewTabSetting: Common.Settings.Setting<NetworkForward.UIRequestLocation.UIRequestTabs>; |
| private readonly headersView: RequestHeadersView; |
| private payloadView: RequestPayloadView|null; |
| private readonly responseView: RequestResponseView|undefined; |
| private cookiesView: RequestCookiesView|null; |
| private initialTab?: NetworkForward.UIRequestLocation.UIRequestTabs; |
| |
| constructor( |
| request: SDK.NetworkRequest.NetworkRequest, calculator: NetworkTimeCalculator, |
| initialTab?: NetworkForward.UIRequestLocation.UIRequestTabs) { |
| super(); |
| this.requestInternal = request; |
| this.element.classList.add('network-item-view'); |
| |
| this.resourceViewTabSetting = Common.Settings.Settings.instance().createSetting( |
| 'resourceViewTab', NetworkForward.UIRequestLocation.UIRequestTabs.Headers); |
| |
| this.headersView = new RequestHeadersView(request); |
| this.appendTab( |
| NetworkForward.UIRequestLocation.UIRequestTabs.Headers, i18nString(UIStrings.headers), this.headersView, |
| i18nString(UIStrings.headers)); |
| |
| this.payloadView = null; |
| void this.maybeAppendPayloadPanel(); |
| |
| this.addEventListener(UI.TabbedPane.Events.TabSelected, this.tabSelected, this); |
| |
| if (request.resourceType() === Common.ResourceType.resourceTypes.WebSocket) { |
| const frameView = new ResourceWebSocketFrameView(request); |
| this.appendTab( |
| NetworkForward.UIRequestLocation.UIRequestTabs.WsFrames, i18nString(UIStrings.messages), frameView, |
| i18nString(UIStrings.websocketMessages)); |
| } else if (request.mimeType === SDK.NetworkRequest.MIME_TYPE.EVENTSTREAM) { |
| this.appendTab( |
| NetworkForward.UIRequestLocation.UIRequestTabs.EventSource, i18nString(UIStrings.eventstream), |
| new EventSourceMessagesView(request)); |
| } else { |
| this.responseView = new RequestResponseView(request); |
| const previewView = new RequestPreviewView(request); |
| this.appendTab( |
| NetworkForward.UIRequestLocation.UIRequestTabs.Preview, i18nString(UIStrings.preview), previewView, |
| i18nString(UIStrings.responsePreview)); |
| const signedExchangeInfo = request.signedExchangeInfo(); |
| if (signedExchangeInfo && signedExchangeInfo.errors && signedExchangeInfo.errors.length) { |
| const icon = UI.Icon.Icon.create('smallicon-error'); |
| UI.Tooltip.Tooltip.install(icon, i18nString(UIStrings.signedexchangeError)); |
| this.setTabIcon(NetworkForward.UIRequestLocation.UIRequestTabs.Preview, icon); |
| } |
| this.appendTab( |
| NetworkForward.UIRequestLocation.UIRequestTabs.Response, i18nString(UIStrings.response), this.responseView, |
| i18nString(UIStrings.rawResponseData)); |
| } |
| |
| this.appendTab( |
| NetworkForward.UIRequestLocation.UIRequestTabs.Initiator, i18nString(UIStrings.initiator), |
| new RequestInitiatorView(request), i18nString(UIStrings.requestInitiatorCallStack)); |
| |
| this.appendTab( |
| NetworkForward.UIRequestLocation.UIRequestTabs.Timing, i18nString(UIStrings.timing), |
| new RequestTimingView(request, calculator), i18nString(UIStrings.requestAndResponseTimeline)); |
| |
| if (request.trustTokenParams()) { |
| this.appendTab( |
| NetworkForward.UIRequestLocation.UIRequestTabs.TrustTokens, i18nString(UIStrings.trustTokens), |
| new NetworkComponents.RequestTrustTokensView.RequestTrustTokensView(request), |
| i18nString(UIStrings.trustTokenOperationDetails)); |
| } |
| |
| this.cookiesView = null; |
| |
| this.initialTab = initialTab || this.resourceViewTabSetting.get(); |
| // Selecting tabs should not be handled by the super class. |
| this.setAutoSelectFirstItemOnShow(false); |
| } |
| |
| wasShown(): void { |
| super.wasShown(); |
| this.requestInternal.addEventListener( |
| SDK.NetworkRequest.Events.RequestHeadersChanged, this.requestHeadersChanged, this); |
| this.requestInternal.addEventListener( |
| SDK.NetworkRequest.Events.ResponseHeadersChanged, this.maybeAppendCookiesPanel, this); |
| this.requestInternal.addEventListener( |
| SDK.NetworkRequest.Events.TrustTokenResultAdded, this.maybeShowErrorIconInTrustTokenTabHeader, this); |
| this.maybeAppendCookiesPanel(); |
| this.maybeShowErrorIconInTrustTokenTabHeader(); |
| |
| // Only select the initial tab the first time the view is shown after construction. |
| // When the view is re-shown (without re-constructing) users or revealers might have changed |
| // the selected tab in the mean time. Show the previously selected tab in that |
| // case instead, by simply doing nohting. |
| if (this.initialTab) { |
| this.selectTabInternal(this.initialTab); |
| this.initialTab = undefined; |
| } |
| } |
| |
| willHide(): void { |
| this.requestInternal.removeEventListener( |
| SDK.NetworkRequest.Events.RequestHeadersChanged, this.requestHeadersChanged, this); |
| this.requestInternal.removeEventListener( |
| SDK.NetworkRequest.Events.ResponseHeadersChanged, this.maybeAppendCookiesPanel, this); |
| this.requestInternal.removeEventListener( |
| SDK.NetworkRequest.Events.TrustTokenResultAdded, this.maybeShowErrorIconInTrustTokenTabHeader, this); |
| } |
| |
| private async requestHeadersChanged(): Promise<void> { |
| this.maybeAppendCookiesPanel(); |
| void this.maybeAppendPayloadPanel(); |
| } |
| |
| private maybeAppendCookiesPanel(): void { |
| const cookiesPresent = this.requestInternal.hasRequestCookies() || this.requestInternal.responseCookies.length > 0; |
| console.assert(cookiesPresent || !this.cookiesView, 'Cookies were introduced in headers and then removed!'); |
| if (cookiesPresent && !this.cookiesView) { |
| this.cookiesView = new RequestCookiesView(this.requestInternal); |
| this.appendTab( |
| NetworkForward.UIRequestLocation.UIRequestTabs.Cookies, i18nString(UIStrings.cookies), this.cookiesView, |
| i18nString(UIStrings.requestAndResponseCookies)); |
| } |
| } |
| |
| private async maybeAppendPayloadPanel(): Promise<void> { |
| if (this.hasTab('payload')) { |
| return; |
| } |
| if (this.requestInternal.queryParameters || await this.requestInternal.requestFormData()) { |
| this.payloadView = new RequestPayloadView(this.requestInternal); |
| this.appendTab( |
| NetworkForward.UIRequestLocation.UIRequestTabs.Payload, i18nString(UIStrings.payload), this.payloadView, |
| i18nString(UIStrings.payload), /* userGesture=*/ void 0, |
| /* isCloseable=*/ void 0, /* isPreviewFeature=*/ void 0, /* index=*/ 1); |
| } |
| } |
| |
| private maybeShowErrorIconInTrustTokenTabHeader(): void { |
| const trustTokenResult = this.requestInternal.trustTokenOperationDoneEvent(); |
| if (trustTokenResult && |
| !NetworkComponents.RequestTrustTokensView.statusConsideredSuccess(trustTokenResult.status)) { |
| this.setTabIcon( |
| NetworkForward.UIRequestLocation.UIRequestTabs.TrustTokens, UI.Icon.Icon.create('smallicon-error')); |
| } |
| } |
| |
| private selectTabInternal(tabId: string): void { |
| if (!this.selectTab(tabId)) { |
| // maybeAppendPayloadPanel might cause payload tab to appear asynchronously, so |
| // it makes sense to retry on the next tick |
| window.setTimeout(() => { |
| if (!this.selectTab(tabId)) { |
| this.selectTab('headers'); |
| } |
| }, 0); |
| } |
| } |
| |
| private tabSelected(event: Common.EventTarget.EventTargetEvent<UI.TabbedPane.EventData>): void { |
| if (!event.data.isUserGesture) { |
| return; |
| } |
| this.resourceViewTabSetting.set(event.data.tabId as NetworkForward.UIRequestLocation.UIRequestTabs); |
| } |
| |
| request(): SDK.NetworkRequest.NetworkRequest { |
| return this.requestInternal; |
| } |
| |
| async revealResponseBody(line?: number): Promise<void> { |
| this.selectTabInternal(NetworkForward.UIRequestLocation.UIRequestTabs.Response); |
| if (this.responseView && typeof line === 'number') { |
| await this.responseView.revealLine((line as number)); |
| } |
| } |
| |
| revealHeader(section: NetworkForward.UIRequestLocation.UIHeaderSection, header: string|undefined): void { |
| this.selectTabInternal(NetworkForward.UIRequestLocation.UIRequestTabs.Headers); |
| this.headersView.revealHeader(section, header); |
| } |
| } |