UNPKG

chrome-devtools-frontend

Version:
845 lines (770 loc) • 32.7 kB
// Copyright 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* * Copyright (C) 2009 Apple Inc. All rights reserved. * Copyright (C) 2009 Joseph Pecoraro * 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: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * 3. Neither the name of Apple Computer, Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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 Platform from '../../../../core/platform/platform.js'; import * as SDK from '../../../../core/sdk/sdk.js'; import * as Protocol from '../../../../generated/protocol.js'; import * as IssuesManager from '../../../../models/issues_manager/issues_manager.js'; import * as NetworkForward from '../../../../panels/network/forward/forward.js'; import * as IconButton from '../../../components/icon_button/icon_button.js'; import * as UI from '../../legacy.js'; import * as DataGrid from '../data_grid/data_grid.js'; import cookiesTableStyles from './cookiesTable.css.js'; const UIStrings = { /** *@description Cookie table cookies table expires session value in Cookies Table of the Cookies table in the Application panel */ session: 'Session', /** *@description Text for the name of something */ name: 'Name', /** *@description Text for the value of something */ value: 'Value', /** *@description Text for the size of something */ size: 'Size', /** *@description Data grid name for Editable Cookies data grid */ editableCookies: 'Editable Cookies', /** *@description Text for web cookies */ cookies: 'Cookies', /** *@description Text for something not available */ na: 'N/A', /** *@description Text for Context Menu entry */ showRequestsWithThisCookie: 'Show requests with this cookie', /** *@description Text for Context Menu entry */ showIssueAssociatedWithThis: 'Show issue associated with this cookie', /** *@description Tooltip for the cell that shows the sourcePort property of a cookie in the cookie table. The source port is numberic attribute of a cookie. */ sourcePortTooltip: 'Shows the source port (range 1-65535) the cookie was set on. If the port is unknown, this shows -1.', /** *@description Tooltip for the cell that shows the sourceScheme property of a cookie in the cookie table. The source scheme is a trinary attribute of a cookie. */ sourceSchemeTooltip: 'Shows the source scheme (`Secure`, `NonSecure`) the cookie was set on. If the scheme is unknown, this shows `Unset`.', /** * @description Text for the date column displayed if the expiration time of the cookie is extremely far out in the future. * @example {+275760-09-13T00:00:00.000Z} date */ timeAfter: 'after {date}', /** * @description Tooltip for the date column displayed if the expiration time of the cookie is extremely far out in the future. * @example {+275760-09-13T00:00:00.000Z} date * @example {9001628746521180} seconds */ timeAfterTooltip: 'The expiration timestamp is {seconds}, which corresponds to a date after {date}', /** * @description Text to be show in the Partition Key column in case it is an opaque origin. */ opaquePartitionKey: '(opaque)', }; const str_ = i18n.i18n.registerUIStrings('ui/legacy/components/cookie_table/CookiesTable.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_); const expiresSessionValue = i18nLazyString(UIStrings.session); export class CookiesTable extends UI.Widget.VBox { private saveCallback?: ((arg0: SDK.Cookie.Cookie, arg1: SDK.Cookie.Cookie|null) => Promise<boolean>); private readonly refreshCallback?: (() => void)|undefined; private readonly deleteCallback?: ((arg0: SDK.Cookie.Cookie, arg1: () => void) => void); private dataGrid: DataGrid.DataGrid.DataGridImpl<DataGridNode>; private lastEditedColumnId: string|null; private data: {folderName: string|null, cookies: Array<SDK.Cookie.Cookie>|null}[]; private cookieDomain: string; private cookieToBlockedReasons: ReadonlyMap<SDK.Cookie.Cookie, SDK.CookieModel.BlockedReason[]>|null; private cookieToExemptionReason: ReadonlyMap<SDK.Cookie.Cookie, SDK.CookieModel.ExemptionReason>|null; constructor( renderInline?: boolean, saveCallback?: ((arg0: SDK.Cookie.Cookie, arg1: SDK.Cookie.Cookie|null) => Promise<boolean>), refreshCallback?: (() => void), selectedCallback?: (() => void), deleteCallback?: ((arg0: SDK.Cookie.Cookie, arg1: () => void) => void)) { super(); this.registerRequiredCSS(cookiesTableStyles); this.element.classList.add('cookies-table'); this.saveCallback = saveCallback; this.refreshCallback = refreshCallback; this.deleteCallback = deleteCallback; const editable = Boolean(saveCallback); const columns = [ { id: SDK.Cookie.Attribute.NAME, title: i18nString(UIStrings.name), sortable: true, disclosure: editable, sort: DataGrid.DataGrid.Order.Ascending, longText: true, weight: 24, editable, }, { id: SDK.Cookie.Attribute.VALUE, title: i18nString(UIStrings.value), sortable: true, longText: true, weight: 34, editable, }, { id: SDK.Cookie.Attribute.DOMAIN, title: 'Domain', sortable: true, weight: 7, editable, }, { id: SDK.Cookie.Attribute.PATH, title: 'Path', sortable: true, weight: 7, editable, }, { id: SDK.Cookie.Attribute.EXPIRES, title: 'Expires / Max-Age', sortable: true, weight: 7, editable, }, { id: SDK.Cookie.Attribute.SIZE, title: i18nString(UIStrings.size), sortable: true, align: DataGrid.DataGrid.Align.RIGHT, weight: 7, }, { id: SDK.Cookie.Attribute.HTTP_ONLY, title: 'HttpOnly', sortable: true, align: DataGrid.DataGrid.Align.CENTER, weight: 7, dataType: DataGrid.DataGrid.DataType.BOOLEAN, editable, }, { id: SDK.Cookie.Attribute.SECURE, title: 'Secure', sortable: true, align: DataGrid.DataGrid.Align.CENTER, weight: 7, dataType: DataGrid.DataGrid.DataType.BOOLEAN, editable, }, { id: SDK.Cookie.Attribute.SAME_SITE, title: 'SameSite', sortable: true, weight: 7, editable, }, { id: SDK.Cookie.Attribute.PARTITION_KEY_SITE, title: 'Partition Key Site', sortable: true, weight: 7, editable, }, { id: SDK.Cookie.Attribute.HAS_CROSS_SITE_ANCESTOR, title: 'Cross Site', sortable: true, align: DataGrid.DataGrid.Align.CENTER, weight: 7, dataType: DataGrid.DataGrid.DataType.BOOLEAN, editable, }, { id: SDK.Cookie.Attribute.PRIORITY, title: 'Priority', sortable: true, weight: 7, editable, }, ] as DataGrid.DataGrid.ColumnDescriptor[]; const config = Common.Settings.Settings.instance().getHostConfig(); if (config.devToolsEnableOriginBoundCookies?.schemeBindingEnabled) { const additionalColumns = [ { id: SDK.Cookie.Attribute.SOURCE_SCHEME, title: 'SourceScheme', sortable: true, align: DataGrid.DataGrid.Align.CENTER, weight: 7, editable, }, ] as DataGrid.DataGrid.ColumnDescriptor[]; columns.push(...additionalColumns); } if (config.devToolsEnableOriginBoundCookies?.portBindingEnabled) { const additionalColumns = [ { id: SDK.Cookie.Attribute.SOURCE_PORT, title: 'SourcePort', sortable: true, align: DataGrid.DataGrid.Align.CENTER, weight: 7, editable, }, ] as DataGrid.DataGrid.ColumnDescriptor[]; columns.push(...additionalColumns); } if (editable) { this.dataGrid = new DataGrid.DataGrid.DataGridImpl({ displayName: i18nString(UIStrings.editableCookies), columns, editCallback: this.onUpdateCookie.bind(this), deleteCallback: this.onDeleteCookie.bind(this), refreshCallback, }); } else { this.dataGrid = new DataGrid.DataGrid.DataGridImpl({ displayName: i18nString(UIStrings.cookies), columns, editCallback: undefined, deleteCallback: undefined, refreshCallback: undefined, }); } this.dataGrid.setStriped(true); this.dataGrid.setName('cookies-table'); this.dataGrid.addEventListener(DataGrid.DataGrid.Events.SORTING_CHANGED, this.rebuildTable, this); this.dataGrid.setRowContextMenuCallback(this.populateContextMenu.bind(this)); if (renderInline) { this.dataGrid.renderInline(); } if (selectedCallback) { this.dataGrid.addEventListener(DataGrid.DataGrid.Events.SELECTED_NODE, selectedCallback, this); } this.lastEditedColumnId = null; this.dataGrid.asWidget().show(this.element); this.data = []; this.cookieDomain = ''; this.cookieToBlockedReasons = null; this.cookieToExemptionReason = null; } setCookies( cookies: SDK.Cookie.Cookie[], cookieToBlockedReasons?: ReadonlyMap<SDK.Cookie.Cookie, SDK.CookieModel.BlockedReason[]>, cookieToExemptionReason?: ReadonlyMap<SDK.Cookie.Cookie, SDK.CookieModel.ExemptionReason>): void { this.setCookieFolders([{cookies, folderName: null}], cookieToBlockedReasons, cookieToExemptionReason); } setCookieFolders( cookieFolders: {folderName: string|null, cookies: Array<SDK.Cookie.Cookie>|null}[], cookieToBlockedReasons?: ReadonlyMap<SDK.Cookie.Cookie, SDK.CookieModel.BlockedReason[]>, cookieToExemptionReason?: ReadonlyMap<SDK.Cookie.Cookie, SDK.CookieModel.ExemptionReason>): void { this.data = cookieFolders; this.cookieToBlockedReasons = cookieToBlockedReasons || null; this.cookieToExemptionReason = cookieToExemptionReason || null; this.rebuildTable(); } setCookieDomain(cookieDomain: string): void { this.cookieDomain = cookieDomain; } selectedCookie(): SDK.Cookie.Cookie|null { const node = this.dataGrid.selectedNode as DataGridNode | null; return node ? node.cookie : null; } private getSelectionCookies(): {current: SDK.Cookie.Cookie|null, neighbor: SDK.Cookie.Cookie|null} { const node = this.dataGrid.selectedNode as DataGridNode | null; const nextNeighbor = node && node.traverseNextNode(true) as DataGridNode | null; const previousNeighbor = node && node.traversePreviousNode(true) as DataGridNode | null; return { current: node && node.cookie, neighbor: (nextNeighbor && nextNeighbor.cookie) || (previousNeighbor && previousNeighbor.cookie), }; } override willHide(): void { this.lastEditedColumnId = null; } private findSelectedCookie( selectionCookies: {current: SDK.Cookie.Cookie|null, neighbor: SDK.Cookie.Cookie|null}, cookies: SDK.Cookie.Cookie[]|null): SDK.Cookie.Cookie|null { if (!cookies) { return null; } const current = selectionCookies.current; const foundCurrent = cookies.find(cookie => this.isSameCookie(cookie, current)); if (foundCurrent) { return foundCurrent; } const neighbor = selectionCookies.neighbor; const foundNeighbor = cookies.find(cookie => this.isSameCookie(cookie, neighbor)); if (foundNeighbor) { return foundNeighbor; } return null; } private isSameCookie(cookieA: SDK.Cookie.Cookie, cookieB: SDK.Cookie.Cookie|null|undefined): boolean { return cookieB !== null && cookieB !== undefined && cookieB.name() === cookieA.name() && cookieB.domain() === cookieA.domain() && cookieB.path() === cookieA.path(); } private rebuildTable(): void { const restoreFocus = this.dataGrid.element?.contains(document.activeElement); const selectionCookies = this.getSelectionCookies(); const lastEditedColumnId = this.lastEditedColumnId; this.lastEditedColumnId = null; this.dataGrid.rootNode().removeChildren(); for (let i = 0; i < this.data.length; ++i) { const item = this.data[i]; const selectedCookie = this.findSelectedCookie(selectionCookies, item.cookies); if (item.folderName) { const groupData = {} as { [x: string]: string | number, }; groupData[SDK.Cookie.Attribute.NAME] = item.folderName; groupData[SDK.Cookie.Attribute.VALUE] = ''; groupData[SDK.Cookie.Attribute.SIZE] = this.totalSize(item.cookies); groupData[SDK.Cookie.Attribute.DOMAIN] = ''; groupData[SDK.Cookie.Attribute.PATH] = ''; groupData[SDK.Cookie.Attribute.EXPIRES] = ''; groupData[SDK.Cookie.Attribute.HTTP_ONLY] = ''; groupData[SDK.Cookie.Attribute.SECURE] = ''; groupData[SDK.Cookie.Attribute.SAME_SITE] = ''; groupData[SDK.Cookie.Attribute.SOURCE_PORT] = ''; groupData[SDK.Cookie.Attribute.SOURCE_SCHEME] = ''; groupData[SDK.Cookie.Attribute.PRIORITY] = ''; const groupNode = new DataGrid.DataGrid.DataGridNode(groupData) as DataGrid.DataGrid.DataGridNode<DataGridNode>; groupNode.selectable = true; this.dataGrid.rootNode().appendChild(groupNode); groupNode.element().classList.add('row-group'); this.populateNode(groupNode, item.cookies, selectedCookie, lastEditedColumnId); groupNode.expand(); } else { this.populateNode(this.dataGrid.rootNode(), item.cookies, selectedCookie, lastEditedColumnId); } } if (selectionCookies.current && lastEditedColumnId && !this.dataGrid.selectedNode) { this.addInactiveNode(this.dataGrid.rootNode(), selectionCookies.current, lastEditedColumnId); } if (this.saveCallback) { this.dataGrid.addCreationNode(false); } if (restoreFocus) { this.dataGrid.element.focus(); } } private populateNode( parentNode: DataGrid.DataGrid.DataGridNode<DataGridNode>, cookies: SDK.Cookie.Cookie[]|null, selectedCookie: SDK.Cookie.Cookie|null, lastEditedColumnId: string|null): void { parentNode.removeChildren(); if (!cookies) { return; } this.sortCookies(cookies); for (let i = 0; i < cookies.length; ++i) { const cookie = cookies[i]; const cookieNode = this.createGridNode(cookie); parentNode.appendChild(cookieNode); if (this.isSameCookie(cookie, selectedCookie)) { cookieNode.select(); if (lastEditedColumnId !== null) { this.dataGrid.startEditingNextEditableColumnOfDataGridNode(cookieNode, lastEditedColumnId); } } } } private addInactiveNode( parentNode: DataGrid.DataGrid.DataGridNode<DataGridNode>, cookie: SDK.Cookie.Cookie, editedColumnId: string|null): void { const cookieNode = this.createGridNode(cookie); parentNode.appendChild(cookieNode); cookieNode.select(); cookieNode.setInactive(true); if (editedColumnId !== null) { this.dataGrid.startEditingNextEditableColumnOfDataGridNode(cookieNode, editedColumnId); } } private totalSize(cookies: SDK.Cookie.Cookie[]|null): number { let totalSize = 0; for (let i = 0; cookies && i < cookies.length; ++i) { totalSize += cookies[i].size(); } return totalSize; } private sortCookies(cookies: SDK.Cookie.Cookie[]): void { const sortDirection = this.dataGrid.isSortOrderAscending() ? 1 : -1; function getValue(cookie: SDK.Cookie.Cookie, property: string): string { switch (property) { case SDK.Cookie.Attribute.NAME: return String(cookie.name()); case SDK.Cookie.Attribute.VALUE: return String(cookie.value()); case SDK.Cookie.Attribute.DOMAIN: return String(cookie.domain()); case SDK.Cookie.Attribute.PATH: return String(cookie.path()); case SDK.Cookie.Attribute.HTTP_ONLY: return String(cookie.httpOnly()); case SDK.Cookie.Attribute.SECURE: return String(cookie.secure()); case SDK.Cookie.Attribute.SAME_SITE: return String(cookie.sameSite()); case SDK.Cookie.Attribute.PARTITION_KEY_SITE: return cookie.partitionKeyOpaque() ? i18nString(UIStrings.opaquePartitionKey) : String(cookie.topLevelSite()); case SDK.Cookie.Attribute.HAS_CROSS_SITE_ANCESTOR: return String(cookie.partitioned() ? cookie.hasCrossSiteAncestor() : false); case SDK.Cookie.Attribute.SOURCE_SCHEME: return String(cookie.sourceScheme()); default: return String(cookie.name()); } } function compareTo(property: string, cookie1: SDK.Cookie.Cookie, cookie2: SDK.Cookie.Cookie): number { return sortDirection * Platform.StringUtilities.compare(getValue(cookie1, property), getValue(cookie2, property)); } function numberCompare( p: (cookie: SDK.Cookie.Cookie) => number, cookie1: SDK.Cookie.Cookie, cookie2: SDK.Cookie.Cookie): number { return sortDirection * (p(cookie1) - p(cookie2)); } function priorityCompare(cookie1: SDK.Cookie.Cookie, cookie2: SDK.Cookie.Cookie): number { const priorities = [ Protocol.Network.CookiePriority.Low, Protocol.Network.CookiePriority.Medium, Protocol.Network.CookiePriority.High, ]; const priority1 = priorities.indexOf(cookie1.priority()); const priority2 = priorities.indexOf(cookie2.priority()); return sortDirection * (priority1 - priority2); } function expiresCompare(cookie1: SDK.Cookie.Cookie, cookie2: SDK.Cookie.Cookie): number { if (cookie1.session() !== cookie2.session()) { return sortDirection * (cookie1.session() ? 1 : -1); } if (cookie1.session()) { return 0; } if (cookie1.maxAge() && cookie2.maxAge()) { return sortDirection * (cookie1.maxAge() - cookie2.maxAge()); } if (cookie1.expires() && cookie2.expires()) { return sortDirection * (cookie1.expires() - cookie2.expires()); } return sortDirection * (cookie1.expires() ? 1 : -1); } let comparator; const columnId = this.dataGrid.sortColumnId() || SDK.Cookie.Attribute.NAME; if (columnId === SDK.Cookie.Attribute.EXPIRES) { comparator = expiresCompare; } else if (columnId === SDK.Cookie.Attribute.SIZE) { comparator = numberCompare.bind(null, c => c.size()); } else if (columnId === SDK.Cookie.Attribute.SOURCE_PORT) { comparator = numberCompare.bind(null, c => c.sourcePort()); } else if (columnId === SDK.Cookie.Attribute.PRIORITY) { comparator = priorityCompare; } else { comparator = compareTo.bind(null, columnId); } cookies.sort(comparator); } private createGridNode(cookie: SDK.Cookie.Cookie): DataGridNode { const data = {} as { [x: string]: string | number | boolean, }; data[SDK.Cookie.Attribute.NAME] = cookie.name(); data[SDK.Cookie.Attribute.VALUE] = cookie.value(); if (cookie.type() === SDK.Cookie.Type.REQUEST) { data[SDK.Cookie.Attribute.DOMAIN] = cookie.domain() ? cookie.domain() : i18nString(UIStrings.na); data[SDK.Cookie.Attribute.PATH] = cookie.path() ? cookie.path() : i18nString(UIStrings.na); } else { data[SDK.Cookie.Attribute.DOMAIN] = cookie.domain() || ''; data[SDK.Cookie.Attribute.PATH] = cookie.path() || ''; } let expiresTooltip = undefined; if (cookie.maxAge()) { data[SDK.Cookie.Attribute.EXPIRES] = i18n.TimeUtilities.secondsToString(Math.floor(cookie.maxAge())); } else if (cookie.expires()) { const expires = cookie.expires(); if (expires < 0) { data[SDK.Cookie.Attribute.EXPIRES] = expiresSessionValue(); } else { // See https://tc39.es/ecma262/#sec-time-values-and-time-range const maxTimestamp: number = 8640000000000000; if (expires > maxTimestamp) { const date = new Date(maxTimestamp).toISOString(); data[SDK.Cookie.Attribute.EXPIRES] = i18nString(UIStrings.timeAfter, {date}); expiresTooltip = i18nString(UIStrings.timeAfterTooltip, {seconds: expires, date}); } else { data[SDK.Cookie.Attribute.EXPIRES] = new Date(expires).toISOString(); } } } else { data[SDK.Cookie.Attribute.EXPIRES] = cookie.type() === SDK.Cookie.Type.REQUEST ? i18nString(UIStrings.na) : expiresSessionValue(); } data[SDK.Cookie.Attribute.SIZE] = cookie.size(); data[SDK.Cookie.Attribute.HTTP_ONLY] = cookie.httpOnly(); data[SDK.Cookie.Attribute.SECURE] = cookie.secure(); data[SDK.Cookie.Attribute.SAME_SITE] = cookie.sameSite() || ''; data[SDK.Cookie.Attribute.SOURCE_PORT] = cookie.sourcePort(); data[SDK.Cookie.Attribute.SOURCE_SCHEME] = cookie.sourceScheme(); data[SDK.Cookie.Attribute.PRIORITY] = cookie.priority() || ''; data[SDK.Cookie.Attribute.PARTITION_KEY_SITE] = cookie.topLevelSite(); data[SDK.Cookie.Attribute.HAS_CROSS_SITE_ANCESTOR] = cookie.hasCrossSiteAncestor() ? 'true' : ''; const blockedReasons = this.cookieToBlockedReasons?.get(cookie); const exemptionReason = this.cookieToExemptionReason?.get(cookie); const node = new DataGridNode(data, cookie, blockedReasons || null, exemptionReason || null); if (expiresTooltip) { node.setExpiresTooltip(expiresTooltip); } node.selectable = true; return node; } private onDeleteCookie(node: DataGridNode): void { if (node.cookie && this.deleteCallback) { this.deleteCallback(node.cookie, () => this.refresh()); } } private onUpdateCookie(editingNode: DataGridNode, columnIdentifier: string, _oldText: string, _newText: string): void { this.lastEditedColumnId = columnIdentifier; this.setDefaults(editingNode); if (this.isValidCookieData(editingNode.data)) { this.saveNode(editingNode); } else { editingNode.setDirty(true); } } private setDefaults(node: DataGridNode): void { if (node.data[SDK.Cookie.Attribute.NAME] === null) { node.data[SDK.Cookie.Attribute.NAME] = ''; } if (node.data[SDK.Cookie.Attribute.VALUE] === null) { node.data[SDK.Cookie.Attribute.VALUE] = ''; } if (node.data[SDK.Cookie.Attribute.DOMAIN] === null) { node.data[SDK.Cookie.Attribute.DOMAIN] = this.cookieDomain; } if (node.data[SDK.Cookie.Attribute.PATH] === null) { node.data[SDK.Cookie.Attribute.PATH] = '/'; } if (node.data[SDK.Cookie.Attribute.EXPIRES] === null) { node.data[SDK.Cookie.Attribute.EXPIRES] = expiresSessionValue(); } if (node.data[SDK.Cookie.Attribute.PARTITION_KEY] === null) { node.data[SDK.Cookie.Attribute.PARTITION_KEY] = ''; } } private saveNode(node: DataGridNode): void { const oldCookie = node.cookie; const newCookie = this.createCookieFromData(node.data); node.cookie = newCookie; if (!this.saveCallback) { return; } void this.saveCallback(newCookie, oldCookie).then(success => { if (success) { this.refresh(); } else { node.setDirty(true); } }); } private createCookieFromData(data: {[x: string]: string}): SDK.Cookie.Cookie { const cookie = new SDK.Cookie.Cookie( data[SDK.Cookie.Attribute.NAME], data[SDK.Cookie.Attribute.VALUE], null, data[SDK.Cookie.Attribute.PRIORITY] as Protocol.Network.CookiePriority); cookie.addAttribute(SDK.Cookie.Attribute.DOMAIN, data[SDK.Cookie.Attribute.DOMAIN]); cookie.addAttribute(SDK.Cookie.Attribute.PATH, data[SDK.Cookie.Attribute.PATH]); if (data.expires && data.expires !== expiresSessionValue()) { cookie.addAttribute(SDK.Cookie.Attribute.EXPIRES, (new Date(data[SDK.Cookie.Attribute.EXPIRES])).toUTCString()); } if (data[SDK.Cookie.Attribute.HTTP_ONLY]) { cookie.addAttribute(SDK.Cookie.Attribute.HTTP_ONLY); } if (data[SDK.Cookie.Attribute.SECURE]) { cookie.addAttribute(SDK.Cookie.Attribute.SECURE); } if (data[SDK.Cookie.Attribute.SAME_SITE]) { cookie.addAttribute(SDK.Cookie.Attribute.SAME_SITE, data[SDK.Cookie.Attribute.SAME_SITE]); } if (SDK.Cookie.Attribute.SOURCE_SCHEME in data) { cookie.addAttribute(SDK.Cookie.Attribute.SOURCE_SCHEME, data[SDK.Cookie.Attribute.SOURCE_SCHEME]); } if (SDK.Cookie.Attribute.SOURCE_PORT in data) { cookie.addAttribute( SDK.Cookie.Attribute.SOURCE_PORT, Number.parseInt(data[SDK.Cookie.Attribute.SOURCE_PORT], 10) || undefined); } if (data[SDK.Cookie.Attribute.PARTITION_KEY_SITE]) { cookie.setPartitionKey( data[SDK.Cookie.Attribute.PARTITION_KEY_SITE], Boolean( data[SDK.Cookie.Attribute.HAS_CROSS_SITE_ANCESTOR] ? data[SDK.Cookie.Attribute.HAS_CROSS_SITE_ANCESTOR] : false)); } cookie.setSize(data[SDK.Cookie.Attribute.NAME].length + data[SDK.Cookie.Attribute.VALUE].length); return cookie; } private isValidCookieData(data: {[x: string]: string}): boolean { return (Boolean(data.name) || Boolean(data.value)) && this.isValidDomain(data.domain) && this.isValidPath(data.path) && this.isValidDate(data.expires) && this.isValidPartitionKey(data.PartitionKeySite); } private isValidDomain(domain: string): boolean { if (!domain) { return true; } const parsedURL = Common.ParsedURL.ParsedURL.fromString('http://' + domain); return parsedURL !== null && parsedURL.domain() === domain; } private isValidPath(path: string): boolean { const parsedURL = Common.ParsedURL.ParsedURL.fromString('http://example.com' + path); return parsedURL !== null && parsedURL.path === path; } private isValidDate(date: string): boolean { return date === '' || date === expiresSessionValue() || !isNaN(Date.parse(date)); } private isValidPartitionKey(partitionKey: string): boolean { if (!partitionKey) { return true; } const parsedURL = Common.ParsedURL.ParsedURL.fromString(partitionKey); return parsedURL !== null; } private refresh(): void { if (this.refreshCallback) { this.refreshCallback(); } } private populateContextMenu( contextMenu: UI.ContextMenu.ContextMenu, gridNode: DataGrid.DataGrid.DataGridNode<DataGridNode>): void { const maybeCookie = (gridNode as DataGridNode).cookie; if (!maybeCookie) { return; } const cookie = maybeCookie; contextMenu.revealSection().appendItem(i18nString(UIStrings.showRequestsWithThisCookie), () => { const requestFilter = NetworkForward.UIFilter.UIRequestFilter.filters([ { filterType: NetworkForward.UIFilter.FilterType.CookieDomain, filterValue: cookie.domain(), }, { filterType: NetworkForward.UIFilter.FilterType.CookieName, filterValue: cookie.name(), }, ]); void Common.Revealer.reveal(requestFilter); }, {jslogContext: 'show-requests-with-this-cookie'}); if (IssuesManager.RelatedIssue.hasIssues(cookie)) { contextMenu.revealSection().appendItem(i18nString(UIStrings.showIssueAssociatedWithThis), () => { // TODO(chromium:1077719): Just filter for the cookie instead of revealing one of the associated issues. void IssuesManager.RelatedIssue.reveal(cookie); }, {jslogContext: 'show-issue-associated-with-this'}); } } } export class DataGridNode extends DataGrid.DataGrid.DataGridNode<DataGridNode> { cookie: SDK.Cookie.Cookie; private readonly blockedReasons: SDK.CookieModel.BlockedReason[]|null; private readonly exemptionReason: SDK.CookieModel.ExemptionReason|null; private expiresTooltip?: Platform.UIString.LocalizedString; constructor( data: {[x: string]: string|number|boolean}, cookie: SDK.Cookie.Cookie, blockedReasons: SDK.CookieModel.BlockedReason[]|null, exemptionReason: SDK.CookieModel.ExemptionReason|null) { super(data); this.cookie = cookie; this.blockedReasons = blockedReasons; this.exemptionReason = exemptionReason; } override createCells(element: Element): void { super.createCells(element); if (this.blockedReasons && this.blockedReasons.length) { element.classList.add('flagged-cookie-attribute-row'); } } setExpiresTooltip(tooltip: Platform.UIString.LocalizedString): void { this.expiresTooltip = tooltip; } override createCell(columnId: string): HTMLElement { const cell = super.createCell(columnId); if (columnId === SDK.Cookie.Attribute.SOURCE_PORT) { UI.Tooltip.Tooltip.install(cell, i18nString(UIStrings.sourcePortTooltip)); } else if (columnId === SDK.Cookie.Attribute.SOURCE_SCHEME) { UI.Tooltip.Tooltip.install(cell, i18nString(UIStrings.sourceSchemeTooltip)); } else if (columnId === SDK.Cookie.Attribute.EXPIRES && this.expiresTooltip) { UI.Tooltip.Tooltip.install(cell, this.expiresTooltip); } else { UI.Tooltip.Tooltip.install(cell, cell.textContent || ''); } let blockedReasonString = ''; if (this.blockedReasons) { for (const blockedReason of this.blockedReasons) { const attributeMatches = blockedReason.attribute === columnId as string; const useNameColumn = !blockedReason.attribute && columnId === SDK.Cookie.Attribute.NAME; if (attributeMatches || useNameColumn) { if (blockedReasonString) { blockedReasonString += '\n'; } blockedReasonString += blockedReason.uiString; } } } if (blockedReasonString) { const infoElement = new IconButton.Icon.Icon(); if (columnId === SDK.Cookie.Attribute.NAME && IssuesManager.RelatedIssue.hasThirdPartyPhaseoutCookieIssue(this.cookie)) { infoElement.data = {iconName: 'warning-filled', color: 'var(--icon-warning)', width: '14px', height: '14px'}; infoElement.onclick = () => IssuesManager.RelatedIssue.reveal(this.cookie); infoElement.style.cursor = 'pointer'; } else { infoElement.data = {iconName: 'info', color: 'var(--icon-info)', width: '14px', height: '14px'}; cell.classList.add('flagged-cookie-attribute-cell'); } infoElement.title = blockedReasonString; cell.insertBefore(infoElement, cell.firstChild); } if (this.exemptionReason?.uiString && columnId === SDK.Cookie.Attribute.NAME) { const infoElement = new IconButton.Icon.Icon(); infoElement.data = {iconName: 'info', color: 'var(--icon-info)', width: '14px', height: '14px'}; cell.classList.add('flagged-cookie-attribute-cell'); infoElement.title = this.exemptionReason.uiString; cell.insertBefore(infoElement, cell.firstChild); } return cell; } }