blob: 051561f18b147eb98851f2718e227e74007685e5 [file] [log] [blame]
/*
* 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())
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');