| /* |
| * 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. |
| */ |
| |
| /** |
| * @unrestricted |
| */ |
| CookieTable.CookiesTable = class extends UI.VBox { |
| /** |
| * @param {function(!SDK.Cookie, ?SDK.Cookie): !Promise<boolean>=} saveCallback |
| * @param {function()=} refreshCallback |
| * @param {function()=} selectedCallback |
| * @param {function(!SDK.Cookie, function())=} deleteCallback |
| */ |
| constructor(saveCallback, refreshCallback, selectedCallback, deleteCallback) { |
| super(); |
| |
| this._saveCallback = saveCallback; |
| this._refreshCallback = refreshCallback; |
| this._deleteCallback = deleteCallback; |
| |
| const editable = !!saveCallback; |
| |
| const columns = /** @type {!Array<!DataGrid.DataGrid.ColumnDescriptor>} */ ([ |
| { |
| id: 'name', |
| title: Common.UIString('Name'), |
| sortable: true, |
| disclosure: editable, |
| sort: DataGrid.DataGrid.Order.Ascending, |
| longText: true, |
| weight: 24, |
| editable: editable |
| }, |
| {id: 'value', title: Common.UIString('Value'), sortable: true, longText: true, weight: 34, editable: editable}, |
| {id: 'domain', title: Common.UIString('Domain'), sortable: true, weight: 7, editable: editable}, |
| {id: 'path', title: Common.UIString('Path'), sortable: true, weight: 7, editable: editable}, |
| {id: 'expires', title: Common.UIString('Expires / Max-Age'), sortable: true, weight: 7, editable: editable}, |
| {id: 'size', title: Common.UIString('Size'), sortable: true, align: DataGrid.DataGrid.Align.Right, weight: 7}, { |
| id: 'httpOnly', |
| title: Common.UIString('HTTP'), |
| sortable: true, |
| align: DataGrid.DataGrid.Align.Center, |
| weight: 7 |
| }, |
| { |
| id: 'secure', |
| title: Common.UIString('Secure'), |
| sortable: true, |
| align: DataGrid.DataGrid.Align.Center, |
| weight: 7 |
| }, |
| { |
| id: 'sameSite', |
| title: Common.UIString('SameSite'), |
| sortable: true, |
| align: DataGrid.DataGrid.Align.Center, |
| weight: 7 |
| } |
| ]); |
| |
| if (editable) { |
| this._dataGrid = new DataGrid.DataGrid( |
| columns, this._onUpdateCookie.bind(this), this._onDeleteCookie.bind(this), refreshCallback); |
| } else { |
| this._dataGrid = new DataGrid.DataGrid(columns); |
| } |
| this._dataGrid.setStriped(true); |
| |
| this._dataGrid.setName('cookiesTable'); |
| this._dataGrid.addEventListener(DataGrid.DataGrid.Events.SortingChanged, this._rebuildTable, this); |
| |
| if (selectedCallback) |
| this._dataGrid.addEventListener(DataGrid.DataGrid.Events.SelectedNode, selectedCallback, this); |
| |
| /** @type {?string} */ |
| this._lastEditedColumnId = null; |
| |
| this._dataGrid.asWidget().show(this.element); |
| this._data = []; |
| |
| /** @type {string} */ |
| this._cookieDomain = ''; |
| } |
| |
| /** |
| * @param {!Array.<!SDK.Cookie>} cookies |
| */ |
| setCookies(cookies) { |
| this.setCookieFolders([{cookies: cookies}]); |
| } |
| |
| /** |
| * @param {!Array.<!{folderName: ?string, cookies: ?Array.<!SDK.Cookie>}>} cookieFolders |
| */ |
| setCookieFolders(cookieFolders) { |
| this._data = cookieFolders; |
| this._rebuildTable(); |
| } |
| |
| /** |
| * @param {string} cookieDomain |
| */ |
| setCookieDomain(cookieDomain) { |
| this._cookieDomain = cookieDomain; |
| } |
| |
| /** |
| * @return {?SDK.Cookie} |
| */ |
| selectedCookie() { |
| const node = this._dataGrid.selectedNode; |
| return node ? node.cookie : null; |
| } |
| |
| /** |
| * @return {{current: ?SDK.Cookie, neighbor: ?SDK.Cookie}} |
| */ |
| _getSelectionCookies() { |
| const node = this._dataGrid.selectedNode; |
| const nextNeighbor = node && node.traverseNextNode(true); |
| const previousNeighbor = node && node.traversePreviousNode(true); |
| |
| return { |
| current: node && node.cookie, |
| neighbor: (nextNeighbor && nextNeighbor.cookie) || (previousNeighbor && previousNeighbor.cookie) |
| }; |
| } |
| |
| /** |
| * @override |
| */ |
| willHide() { |
| this._lastEditedColumnId = null; |
| } |
| |
| /** |
| * @param {{current: ?SDK.Cookie, neighbor: ?SDK.Cookie}} selectionCookies |
| * @param {?Array<!SDK.Cookie>} cookies |
| * @return {?SDK.Cookie} |
| */ |
| _findSelectedCookie(selectionCookies, cookies) { |
| 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; |
| } |
| |
| /** |
| * @param {!SDK.Cookie} cookieA |
| * @param {?SDK.Cookie} cookieB |
| * @return {boolean} |
| */ |
| _isSameCookie(cookieA, cookieB) { |
| return !!cookieB && cookieB.name() === cookieA.name() && cookieB.domain() === cookieA.domain() && |
| cookieB.path() === cookieA.path(); |
| } |
| |
| _rebuildTable() { |
| 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 = { |
| name: item.folderName, |
| value: '', |
| domain: '', |
| path: '', |
| expires: '', |
| size: this._totalSize(item.cookies), |
| httpOnly: '', |
| secure: '', |
| sameSite: '' |
| }; |
| const groupNode = new DataGrid.DataGridNode(groupData); |
| 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); |
| } |
| |
| /** |
| * @param {!DataGrid.DataGridNode} parentNode |
| * @param {?Array.<!SDK.Cookie>} cookies |
| * @param {?SDK.Cookie} selectedCookie |
| * @param {?string} lastEditedColumnId |
| */ |
| _populateNode(parentNode, cookies, selectedCookie, lastEditedColumnId) { |
| 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); |
| } |
| } |
| } |
| |
| /** |
| * @param {!DataGrid.DataGridNode} parentNode |
| * @param {!SDK.Cookie} cookie |
| * @param {?string} editedColumnId |
| */ |
| _addInactiveNode(parentNode, cookie, editedColumnId) { |
| const cookieNode = this._createGridNode(cookie); |
| parentNode.appendChild(cookieNode); |
| cookieNode.select(); |
| cookieNode.setInactive(true); |
| if (editedColumnId !== null) |
| this._dataGrid.startEditingNextEditableColumnOfDataGridNode(cookieNode, editedColumnId); |
| } |
| |
| _totalSize(cookies) { |
| let totalSize = 0; |
| for (let i = 0; cookies && i < cookies.length; ++i) |
| totalSize += cookies[i].size(); |
| return totalSize; |
| } |
| |
| /** |
| * @param {!Array.<!SDK.Cookie>} cookies |
| */ |
| _sortCookies(cookies) { |
| const sortDirection = this._dataGrid.isSortOrderAscending() ? 1 : -1; |
| |
| /** |
| * @param {!SDK.Cookie} cookie |
| * @param {string} property |
| * @return {string} |
| */ |
| function getValue(cookie, property) { |
| return typeof cookie[property] === 'function' ? String(cookie[property]()) : String(cookie.name()); |
| } |
| |
| /** |
| * @param {string} property |
| * @param {!SDK.Cookie} cookie1 |
| * @param {!SDK.Cookie} cookie2 |
| */ |
| function compareTo(property, cookie1, cookie2) { |
| return sortDirection * getValue(cookie1, property).compareTo(getValue(cookie2, property)); |
| } |
| |
| /** |
| * @param {!SDK.Cookie} cookie1 |
| * @param {!SDK.Cookie} cookie2 |
| */ |
| function numberCompare(cookie1, cookie2) { |
| return sortDirection * (cookie1.size() - cookie2.size()); |
| } |
| |
| /** |
| * @param {!SDK.Cookie} cookie1 |
| * @param {!SDK.Cookie} cookie2 |
| */ |
| function expiresCompare(cookie1, cookie2) { |
| 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() || 'name'; |
| if (columnId === 'expires') |
| comparator = expiresCompare; |
| else if (columnId === 'size') |
| comparator = numberCompare; |
| else |
| comparator = compareTo.bind(null, columnId); |
| cookies.sort(comparator); |
| } |
| |
| /** |
| * @param {!SDK.Cookie} cookie |
| * @return {!DataGrid.DataGridNode} |
| */ |
| _createGridNode(cookie) { |
| const data = {}; |
| data.name = cookie.name(); |
| data.value = cookie.value(); |
| if (cookie.type() === SDK.Cookie.Type.Request) { |
| data.domain = Common.UIString('N/A'); |
| data.path = Common.UIString('N/A'); |
| data.expires = Common.UIString('N/A'); |
| } else { |
| data.domain = cookie.domain() || ''; |
| data.path = cookie.path() || ''; |
| if (cookie.maxAge()) { |
| data.expires = Number.secondsToString(parseInt(cookie.maxAge(), 10)); |
| } else if (cookie.expires()) { |
| if (cookie.expires() < 0) |
| data.expires = 'N/A'; |
| else |
| data.expires = new Date(cookie.expires()).toISOString(); |
| } else { |
| data.expires = CookieTable.CookiesTable._expiresSessionValue; |
| } |
| } |
| data.size = cookie.size(); |
| const checkmark = '\u2713'; |
| data.httpOnly = (cookie.httpOnly() ? checkmark : ''); |
| data.secure = (cookie.secure() ? checkmark : ''); |
| data.sameSite = cookie.sameSite() || ''; |
| |
| const node = new DataGrid.DataGridNode(data); |
| node.cookie = cookie; |
| node.selectable = true; |
| return node; |
| } |
| |
| /** |
| * @param {!DataGrid.DataGridNode} node |
| */ |
| _onDeleteCookie(node) { |
| if (node.cookie && this._deleteCallback) |
| this._deleteCallback(node.cookie, () => this._refresh()); |
| } |
| |
| /** |
| * @param {!DataGrid.DataGridNode} editingNode |
| * @param {string} columnIdentifier |
| * @param {string} oldText |
| * @param {string} newText |
| */ |
| _onUpdateCookie(editingNode, columnIdentifier, oldText, newText) { |
| this._lastEditedColumnId = columnIdentifier; |
| this._setDefaults(editingNode); |
| if (this._isValidCookieData(editingNode.data)) |
| this._saveNode(editingNode); |
| else |
| editingNode.setDirty(true); |
| } |
| |
| /** |
| * @param {!DataGrid.DataGridNode} node |
| */ |
| _setDefaults(node) { |
| if (node.data.name === null) |
| node.data.name = ''; |
| if (node.data.value === null) |
| node.data.value = ''; |
| if (node.data.domain === null) |
| node.data.domain = this._cookieDomain; |
| if (node.data.path === null) |
| node.data.path = '/'; |
| if (node.data.expires === null) |
| node.data.expires = CookieTable.CookiesTable._expiresSessionValue; |
| } |
| |
| /** |
| * @param {!DataGrid.DataGridNode} node |
| */ |
| _saveNode(node) { |
| const oldCookie = node.cookie; |
| const newCookie = this._createCookieFromData(node.data); |
| node.cookie = newCookie; |
| this._saveCallback(newCookie, oldCookie).then(success => { |
| if (success) |
| this._refresh(); |
| else |
| node.setDirty(true); |
| }); |
| } |
| |
| /** |
| * @param {!Object.<string, *>} data |
| * @returns {!SDK.Cookie} |
| */ |
| _createCookieFromData(data) { |
| const cookie = new SDK.Cookie(data.name, data.value, null); |
| cookie.addAttribute('domain', data.domain); |
| cookie.addAttribute('path', data.path); |
| if (data.expires && data.expires !== CookieTable.CookiesTable._expiresSessionValue) |
| cookie.addAttribute('expires', (new Date(data.expires)).toUTCString()); |
| if (data.httpOnly) |
| cookie.addAttribute('httpOnly'); |
| if (data.secure) |
| cookie.addAttribute('secure'); |
| if (data.sameSite) |
| cookie.addAttribute('sameSite', data.sameSite); |
| cookie.setSize(data.name.length + data.value.length); |
| return cookie; |
| } |
| |
| /** |
| * @param {!Object.<string, *>} data |
| * @returns {boolean} |
| */ |
| _isValidCookieData(data) { |
| return (data.name || data.value) && this._isValidDomain(data.domain) && this._isValidPath(data.path) && |
| this._isValidDate(data.expires); |
| } |
| |
| /** |
| * @param {string} domain |
| * @returns {boolean} |
| */ |
| _isValidDomain(domain) { |
| if (!domain) |
| return true; |
| const parsedURL = ('http://' + domain).asParsedURL(); |
| return !!parsedURL && parsedURL.domain() === domain; |
| } |
| |
| /** |
| * @param {string} path |
| * @returns {boolean} |
| */ |
| _isValidPath(path) { |
| const parsedURL = ('http://example.com' + path).asParsedURL(); |
| return !!parsedURL && parsedURL.path === path; |
| } |
| |
| /** |
| * @param {string} date |
| * @returns {boolean} |
| */ |
| _isValidDate(date) { |
| return date === '' || date === CookieTable.CookiesTable._expiresSessionValue || !isNaN(Date.parse(date)); |
| } |
| |
| _refresh() { |
| if (this._refreshCallback) |
| this._refreshCallback(); |
| } |
| }; |
| |
| /** @const */ |
| CookieTable.CookiesTable._expiresSessionValue = Common.UIString('Session'); |