chrome-devtools-frontend
Version:
Chrome DevTools UI
338 lines (308 loc) • 15.1 kB
text/typescript
/*
* 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 Root from '../../core/root/root.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as NetworkForward from '../../panels/network/forward/forward.js';
import * as IconButton from '../../ui/components/icon_button/icon_button.js';
import * as LegacyWrapper from '../../ui/components/legacy_wrapper/legacy_wrapper.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. Previously known as 'Trust Tokens'.
*/
trustTokens: 'Private State Tokens',
/**
*@description Title of the Private State Token tab in the Network panel. Previously known as 'Trust Token tab'.
*/
trustTokenOperationDetails: 'Private State 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 readonly headersViewComponent: NetworkComponents.RequestHeadersView.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');
const headersTab = Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.HEADER_OVERRIDES) ?
NetworkForward.UIRequestLocation.UIRequestTabs.HeadersComponent :
NetworkForward.UIRequestLocation.UIRequestTabs.Headers;
this.resourceViewTabSetting = Common.Settings.Settings.instance().createSetting('resourceViewTab', headersTab);
this.headersView = new RequestHeadersView(request);
this.headersViewComponent = new NetworkComponents.RequestHeadersView.RequestHeadersView(request);
if (Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.HEADER_OVERRIDES)) {
this.appendTab(
headersTab, i18nString(UIStrings.headers),
LegacyWrapper.LegacyWrapper.legacyWrapper(UI.Widget.VBox, this.headersViewComponent),
i18nString(UIStrings.headers));
} else {
this.appendTab(headersTab, 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 = new IconButton.Icon.Icon();
icon.data = {iconName: 'cross-circle-filled', color: 'var(--icon-error)', width: '14px', height: '14px'};
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),
LegacyWrapper.LegacyWrapper.legacyWrapper(
UI.Widget.VBox, 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);
}
override 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;
}
}
override 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)) {
const icon = new IconButton.Icon.Icon();
icon.data = {iconName: 'cross-circle-filled', color: 'var(--icon-error)', width: '14px', height: '14px'};
this.setTabIcon(NetworkForward.UIRequestLocation.UIRequestTabs.TrustTokens, icon);
}
}
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 {
if (Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.HEADER_OVERRIDES)) {
this.selectTabInternal(NetworkForward.UIRequestLocation.UIRequestTabs.HeadersComponent);
this.headersViewComponent.revealHeader(section, header);
} else {
this.selectTabInternal(NetworkForward.UIRequestLocation.UIRequestTabs.Headers);
this.headersView.revealHeader(section, header);
}
}
getHeadersView(): RequestHeadersView {
return this.headersView;
}
getHeadersViewComponent(): NetworkComponents.RequestHeadersView.RequestHeadersView {
return this.headersViewComponent;
}
}