| /* |
| * Copyright (C) 2011 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. |
| */ |
| /* eslint-disable rulesdir/no-imperative-dom-api */ |
| |
| 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 Protocol from '../../generated/protocol.js'; |
| import * as IconButton from '../../ui/components/icon_button/icon_button.js'; |
| import * as CookieTable from '../../ui/legacy/components/cookie_table/cookie_table.js'; |
| import * as UI from '../../ui/legacy/legacy.js'; |
| import * as VisualLogging from '../../ui/visual_logging/visual_logging.js'; |
| |
| import requestCookiesViewStyles from './requestCookiesView.css.js'; |
| |
| const UIStrings = { |
| /** |
| *@description Text in Request Cookies View of the Network panel |
| */ |
| thisRequestHasNoCookies: 'This request has no cookies.', |
| /** |
| * @description Title for a table which shows all of the cookies associated with a selected network |
| * request, in the Network panel. Noun phrase. |
| */ |
| requestCookies: 'Request Cookies', |
| /** |
| *@description Tooltip to explain what request cookies are |
| */ |
| cookiesThatWereSentToTheServerIn: 'Cookies that were sent to the server in the \'cookie\' header of the request', |
| /** |
| *@description Label for showing request cookies that were not actually sent |
| */ |
| showFilteredOutRequestCookies: 'show filtered out request cookies', |
| /** |
| *@description Text in Request Headers View of the Network Panel |
| */ |
| noRequestCookiesWereSent: 'No request cookies were sent.', |
| /** |
| *@description Text in Request Cookies View of the Network panel |
| */ |
| responseCookies: 'Response Cookies', |
| /** |
| *@description Tooltip to explain what response cookies are |
| */ |
| cookiesThatWereReceivedFromThe: |
| 'Cookies that were received from the server in the \'`set-cookie`\' header of the response', |
| /** |
| *@description Label for response cookies with invalid syntax |
| */ |
| malformedResponseCookies: 'Malformed Response Cookies', |
| /** |
| * @description Tooltip to explain what malformed response cookies are. Malformed cookies are |
| * cookies that did not match the expected format and could not be interpreted, and are invalid. |
| */ |
| cookiesThatWereReceivedFromTheServer: |
| 'Cookies that were received from the server in the \'`set-cookie`\' header of the response but were malformed', |
| |
| /** |
| * @description Informational text to explain that there were other cookies |
| * that were not used and not shown in the list. |
| * @example {Learn more} PH1 |
| * |
| */ |
| siteHasCookieInOtherPartition: |
| 'This site has cookies in another partition, that were not sent with this request. {PH1}', |
| /** |
| * @description Title of a link to the developer documentation. |
| */ |
| learnMore: 'Learn more', |
| } as const; |
| const str_ = i18n.i18n.registerUIStrings('panels/network/RequestCookiesView.ts', UIStrings); |
| const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); |
| |
| export class RequestCookiesView extends UI.Widget.Widget { |
| private request: SDK.NetworkRequest.NetworkRequest; |
| private readonly showFilteredOutCookiesSetting: Common.Settings.Setting<boolean>; |
| private readonly emptyWidget: UI.EmptyWidget.EmptyWidget; |
| private readonly requestCookiesTitle: HTMLElement; |
| private readonly requestCookiesEmpty: HTMLElement; |
| private readonly requestCookiesTable: CookieTable.CookiesTable.CookiesTable; |
| private readonly responseCookiesTitle: HTMLElement; |
| private readonly responseCookiesTable: CookieTable.CookiesTable.CookiesTable; |
| private readonly siteHasCookieInOtherPartition: HTMLElement; |
| private readonly malformedResponseCookiesTitle: HTMLElement; |
| private readonly malformedResponseCookiesList: HTMLElement; |
| |
| constructor(request: SDK.NetworkRequest.NetworkRequest) { |
| super(); |
| this.registerRequiredCSS(requestCookiesViewStyles); |
| |
| this.element.classList.add('request-cookies-view'); |
| this.element.setAttribute('jslog', `${VisualLogging.pane('cookies').track({resize: true})}`); |
| |
| this.request = request; |
| this.showFilteredOutCookiesSetting = Common.Settings.Settings.instance().createSetting( |
| 'show-filtered-out-request-cookies', /* defaultValue */ false); |
| |
| this.emptyWidget = new UI.EmptyWidget.EmptyWidget(i18nString(UIStrings.thisRequestHasNoCookies), ''); |
| this.emptyWidget.show(this.element); |
| |
| this.requestCookiesTitle = this.element.createChild('div'); |
| const titleText = this.requestCookiesTitle.createChild('span', 'request-cookies-title'); |
| titleText.textContent = i18nString(UIStrings.requestCookies); |
| UI.Tooltip.Tooltip.install(titleText, i18nString(UIStrings.cookiesThatWereSentToTheServerIn)); |
| |
| const requestCookiesCheckbox = UI.SettingsUI.createSettingCheckbox( |
| i18nString(UIStrings.showFilteredOutRequestCookies), this.showFilteredOutCookiesSetting); |
| requestCookiesCheckbox.addEventListener('change', () => { |
| this.refreshRequestCookiesView(); |
| }); |
| this.requestCookiesTitle.appendChild(requestCookiesCheckbox); |
| |
| this.requestCookiesEmpty = this.element.createChild('div', 'cookies-panel-item'); |
| this.requestCookiesEmpty.textContent = i18nString(UIStrings.noRequestCookiesWereSent); |
| |
| this.requestCookiesTable = new CookieTable.CookiesTable.CookiesTable(/* renderInline */ true); |
| this.requestCookiesTable.contentElement.classList.add('cookie-table', 'cookies-panel-item'); |
| this.requestCookiesTable.show(this.element); |
| |
| this.siteHasCookieInOtherPartition = |
| this.element.createChild('div', 'cookies-panel-item site-has-cookies-in-other-partition'); |
| this.siteHasCookieInOtherPartition.appendChild( |
| i18n.i18n.getFormatLocalizedString(str_, UIStrings.siteHasCookieInOtherPartition, { |
| PH1: UI.XLink.XLink.create( |
| 'https://developer.chrome.com/en/docs/privacy-sandbox/chips/', i18nString(UIStrings.learnMore), undefined, |
| undefined, 'learn-more'), |
| })); |
| |
| this.responseCookiesTitle = this.element.createChild('div', 'request-cookies-title'); |
| this.responseCookiesTitle.textContent = i18nString(UIStrings.responseCookies); |
| this.responseCookiesTitle.title = i18nString(UIStrings.cookiesThatWereReceivedFromThe); |
| |
| this.responseCookiesTable = new CookieTable.CookiesTable.CookiesTable(/* renderInline */ true); |
| this.responseCookiesTable.contentElement.classList.add('cookie-table', 'cookies-panel-item'); |
| this.responseCookiesTable.show(this.element); |
| |
| this.malformedResponseCookiesTitle = this.element.createChild('div', 'request-cookies-title'); |
| this.malformedResponseCookiesTitle.textContent = i18nString(UIStrings.malformedResponseCookies); |
| UI.Tooltip.Tooltip.install( |
| this.malformedResponseCookiesTitle, i18nString(UIStrings.cookiesThatWereReceivedFromTheServer)); |
| |
| this.malformedResponseCookiesList = this.element.createChild('div'); |
| } |
| |
| private getRequestCookies(): { |
| requestCookies: SDK.Cookie.Cookie[], |
| requestCookieToBlockedReasons: Map<SDK.Cookie.Cookie, SDK.CookieModel.BlockedReason[]>, |
| requestCookieToExemptionReason: Map<SDK.Cookie.Cookie, SDK.CookieModel.ExemptionReason>, |
| } { |
| const requestCookieToBlockedReasons = new Map<SDK.Cookie.Cookie, SDK.CookieModel.BlockedReason[]>(); |
| const requestCookieToExemptionReason = new Map<SDK.Cookie.Cookie, SDK.CookieModel.ExemptionReason>(); |
| const requestCookies = |
| this.request.includedRequestCookies().map(includedRequestCookie => includedRequestCookie.cookie); |
| |
| if (this.showFilteredOutCookiesSetting.get()) { |
| for (const blockedCookie of this.request.blockedRequestCookies()) { |
| requestCookieToBlockedReasons.set(blockedCookie.cookie, blockedCookie.blockedReasons.map(blockedReason => { |
| return { |
| attribute: SDK.NetworkRequest.cookieBlockedReasonToAttribute(blockedReason), |
| uiString: SDK.NetworkRequest.cookieBlockedReasonToUiString(blockedReason), |
| }; |
| })); |
| requestCookies.push(blockedCookie.cookie); |
| } |
| } |
| for (const includedCookie of this.request.includedRequestCookies()) { |
| if (includedCookie.exemptionReason) { |
| requestCookieToExemptionReason.set(includedCookie.cookie, { |
| uiString: SDK.NetworkRequest.cookieExemptionReasonToUiString(includedCookie.exemptionReason), |
| }); |
| } |
| } |
| return {requestCookies, requestCookieToBlockedReasons, requestCookieToExemptionReason}; |
| } |
| |
| private getResponseCookies(): { |
| responseCookies: SDK.Cookie.Cookie[], |
| responseCookieToBlockedReasons: Map<SDK.Cookie.Cookie, SDK.CookieModel.BlockedReason[]>, |
| responseCookieToExemptionReason: Map<SDK.Cookie.Cookie, SDK.CookieModel.ExemptionReason>, |
| malformedResponseCookies: SDK.NetworkRequest.BlockedSetCookieWithReason[], |
| } { |
| let responseCookies: SDK.Cookie.Cookie[] = []; |
| const responseCookieToBlockedReasons = new Map<SDK.Cookie.Cookie, SDK.CookieModel.BlockedReason[]>(); |
| const responseCookieToExemptionReason = new Map<SDK.Cookie.Cookie, SDK.CookieModel.ExemptionReason>(); |
| const malformedResponseCookies: SDK.NetworkRequest.BlockedSetCookieWithReason[] = []; |
| |
| if (this.request.responseCookies.length) { |
| responseCookies = this.request.nonBlockedResponseCookies(); |
| for (const blockedCookie of this.request.blockedResponseCookies()) { |
| const parsedCookies = SDK.CookieParser.CookieParser.parseSetCookie(blockedCookie.cookieLine); |
| if ((parsedCookies && !parsedCookies.length) || |
| blockedCookie.blockedReasons.includes(Protocol.Network.SetCookieBlockedReason.SyntaxError) || |
| blockedCookie.blockedReasons.includes( |
| Protocol.Network.SetCookieBlockedReason.NameValuePairExceedsMaxSize)) { |
| malformedResponseCookies.push(blockedCookie); |
| continue; |
| } |
| |
| let cookie: SDK.Cookie.Cookie|(SDK.Cookie.Cookie | null) = blockedCookie.cookie; |
| if (!cookie && parsedCookies) { |
| cookie = parsedCookies[0]; |
| } |
| if (cookie) { |
| responseCookieToBlockedReasons.set(cookie, blockedCookie.blockedReasons.map(blockedReason => { |
| return { |
| attribute: SDK.NetworkRequest.setCookieBlockedReasonToAttribute(blockedReason), |
| uiString: SDK.NetworkRequest.setCookieBlockedReasonToUiString(blockedReason), |
| }; |
| })); |
| responseCookies.push(cookie); |
| } |
| } |
| for (const exemptedCookie of this.request.exemptedResponseCookies()) { |
| // `responseCookies` are generated from `Set-Cookie` header, which should include the exempted cookies, whereas |
| // exempted cookies are received via CDP as objects of type cookie. Therefore they are different objects in |
| // DevTools and need to be matched here in order for the rendering logic to be able to lookup a potential |
| // exemption reason for a cookie. |
| const matchedResponseCookie = |
| responseCookies.find(responseCookie => exemptedCookie.cookieLine === responseCookie.getCookieLine()); |
| if (matchedResponseCookie) { |
| responseCookieToExemptionReason.set(matchedResponseCookie, { |
| uiString: SDK.NetworkRequest.cookieExemptionReasonToUiString(exemptedCookie.exemptionReason), |
| }); |
| } |
| } |
| } |
| |
| return {responseCookies, responseCookieToBlockedReasons, responseCookieToExemptionReason, malformedResponseCookies}; |
| } |
| |
| private refreshRequestCookiesView(): void { |
| if (!this.isShowing()) { |
| return; |
| } |
| |
| const gotCookies = this.request.hasRequestCookies() || this.request.responseCookies.length; |
| if (gotCookies) { |
| this.emptyWidget.hideWidget(); |
| } else { |
| this.emptyWidget.showWidget(); |
| } |
| |
| const {requestCookies, requestCookieToBlockedReasons, requestCookieToExemptionReason} = this.getRequestCookies(); |
| const {responseCookies, responseCookieToBlockedReasons, responseCookieToExemptionReason, malformedResponseCookies} = |
| this.getResponseCookies(); |
| |
| if (requestCookies.length) { |
| this.requestCookiesTitle.classList.remove('hidden'); |
| this.requestCookiesEmpty.classList.add('hidden'); |
| this.requestCookiesTable.showWidget(); |
| this.requestCookiesTable.setCookies( |
| requestCookies, requestCookieToBlockedReasons, requestCookieToExemptionReason); |
| |
| } else if (this.request.blockedRequestCookies().length) { |
| this.requestCookiesTitle.classList.remove('hidden'); |
| this.requestCookiesEmpty.classList.remove('hidden'); |
| this.requestCookiesTable.hideWidget(); |
| |
| } else { |
| this.requestCookiesTitle.classList.add('hidden'); |
| this.requestCookiesEmpty.classList.add('hidden'); |
| this.requestCookiesTable.hideWidget(); |
| } |
| |
| if (responseCookies.length) { |
| this.responseCookiesTitle.classList.remove('hidden'); |
| this.responseCookiesTable.showWidget(); |
| this.responseCookiesTable.setCookies( |
| responseCookies, responseCookieToBlockedReasons, responseCookieToExemptionReason); |
| } else { |
| this.responseCookiesTitle.classList.add('hidden'); |
| this.responseCookiesTable.hideWidget(); |
| } |
| |
| if (malformedResponseCookies.length) { |
| this.malformedResponseCookiesTitle.classList.remove('hidden'); |
| this.malformedResponseCookiesList.classList.remove('hidden'); |
| |
| this.malformedResponseCookiesList.removeChildren(); |
| for (const malformedCookie of malformedResponseCookies) { |
| const listItem = this.malformedResponseCookiesList.createChild('span', 'cookie-line source-code'); |
| const icon = new IconButton.Icon.Icon(); |
| icon.data = {iconName: 'cross-circle-filled', color: 'var(--icon-error)', width: '14px', height: '14px'}; |
| icon.classList.add('cookie-warning-icon'); |
| listItem.appendChild(icon); |
| UI.UIUtils.createTextChild(listItem, malformedCookie.cookieLine); |
| |
| if (malformedCookie.blockedReasons.includes( |
| Protocol.Network.SetCookieBlockedReason.NameValuePairExceedsMaxSize)) { |
| listItem.title = SDK.NetworkRequest.setCookieBlockedReasonToUiString( |
| Protocol.Network.SetCookieBlockedReason.NameValuePairExceedsMaxSize); |
| } else { |
| listItem.title = |
| SDK.NetworkRequest.setCookieBlockedReasonToUiString(Protocol.Network.SetCookieBlockedReason.SyntaxError); |
| } |
| } |
| } else { |
| this.malformedResponseCookiesTitle.classList.add('hidden'); |
| this.malformedResponseCookiesList.classList.add('hidden'); |
| } |
| |
| if (this.request.siteHasCookieInOtherPartition()) { |
| this.siteHasCookieInOtherPartition.classList.remove('hidden'); |
| } else { |
| this.siteHasCookieInOtherPartition.classList.add('hidden'); |
| } |
| } |
| |
| override wasShown(): void { |
| super.wasShown(); |
| this.request.addEventListener( |
| SDK.NetworkRequest.Events.REQUEST_HEADERS_CHANGED, this.refreshRequestCookiesView, this); |
| this.request.addEventListener( |
| SDK.NetworkRequest.Events.RESPONSE_HEADERS_CHANGED, this.refreshRequestCookiesView, this); |
| |
| this.refreshRequestCookiesView(); |
| } |
| |
| override willHide(): void { |
| this.request.removeEventListener( |
| SDK.NetworkRequest.Events.REQUEST_HEADERS_CHANGED, this.refreshRequestCookiesView, this); |
| this.request.removeEventListener( |
| SDK.NetworkRequest.Events.RESPONSE_HEADERS_CHANGED, this.refreshRequestCookiesView, this); |
| } |
| } |