blob: 8c575d6ca1e73393ff576a85542138cf3b30bd88 [file] [log] [blame]
/*
* Copyright (C) 2013, 2015, 2018 Apple 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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.
*/
WI.CookieStorageContentView = class CookieStorageContentView extends WI.ContentView
{
constructor(representedObject)
{
super(representedObject);
this.element.classList.add("cookie-storage");
this._cookies = [];
this._filteredCookies = [];
this._sortComparator = null;
this._table = null;
this._knownCells = new WeakSet;
this._emptyFilterResultsMessageElement = null;
this._filterBarNavigationItem = new WI.FilterBarNavigationItem;
this._filterBarNavigationItem.filterBar.addEventListener(WI.FilterBar.Event.FilterDidChange, this._handleFilterBarFilterDidChange, this);
if (InspectorBackend.hasCommand("Page.setCookie")) {
this._setCookieButtonNavigationItem = new WI.ButtonNavigationItem("cookie-storage-set-cookie", WI.UIString("Add Cookie"), "Images/Plus15.svg", 15, 15);
this._setCookieButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleSetCookieButtonClick, this);
}
if (InspectorBackend.hasCommand("Page.getCookies")) {
this._refreshButtonNavigationItem = new WI.ButtonNavigationItem("cookie-storage-refresh", WI.UIString("Refresh"), "Images/ReloadFull.svg", 13, 13);
this._refreshButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._refreshButtonClicked, this);
}
if (InspectorBackend.hasCommand("Page.deleteCookie")) {
this._clearButtonNavigationItem = new WI.ButtonNavigationItem("cookie-storage-clear", WI.UIString("Clear Cookies"), "Images/NavigationItemTrash.svg", 15, 15);
this._clearButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
this._clearButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._handleClearNavigationItemClicked, this);
}
}
// Public
get navigationItems()
{
let navigationItems = [];
navigationItems.push(this._filterBarNavigationItem);
navigationItems.push(new WI.DividerNavigationItem);
if (this._setCookieButtonNavigationItem)
navigationItems.push(this._setCookieButtonNavigationItem);
if (this._refreshButtonNavigationItem)
navigationItems.push(this._refreshButtonNavigationItem);
if (this._clearButtonNavigationItem)
navigationItems.push(this._clearButtonNavigationItem);
return navigationItems;
}
saveToCookie(cookie)
{
cookie.type = WI.ContentViewCookieType.CookieStorage;
cookie.host = this.representedObject.host;
}
get scrollableElements()
{
if (!this._table)
return [];
return [this._table.scrollContainer];
}
get canFocusFilterBar()
{
return true;
}
focusFilterBar()
{
this._filterBarNavigationItem.filterBar.focus();
}
handleCopyEvent(event)
{
if (!this._table || !this._table.selectedRows.length)
return;
let cookies = this._cookiesAtIndexes(this._table.selectedRows);
if (!cookies.length)
return;
event.clipboardData.setData("text/plain", this._formatCookiesAsText(cookies));
event.stopPropagation();
event.preventDefault();
}
// Table dataSource
tableIndexForRepresentedObject(table, object)
{
let index = this._filteredCookies.indexOf(object);
console.assert(index >= 0);
return index;
}
tableRepresentedObjectForIndex(table, index)
{
console.assert(index >= 0 && index < this._filteredCookies.length);
return this._filteredCookies[index];
}
tableNumberOfRows(table)
{
return this._filteredCookies.length;
}
tableSortChanged(table)
{
this._generateSortComparator();
if (!this._sortComparator)
return;
this._updateSort();
this._updateFilteredCookies();
this._updateEmptyFilterResultsMessage();
this._table.reloadData();
}
// Table delegate
tableCellContextMenuClicked(table, cell, column, rowIndex, event)
{
let contextMenu = WI.ContextMenu.createFromEvent(event);
contextMenu.appendSeparator();
if (InspectorBackend.hasCommand("Page.setCookie") && column.identifier !== "size") {
contextMenu.appendItem(WI.UIString("Edit %s").format(column.name), () => {
this._showCookiePopover(cell, this._filteredCookies[rowIndex], column.identifier);
});
}
contextMenu.appendItem(WI.UIString("Copy"), () => {
let rowIndexes;
if (table.isRowSelected(rowIndex))
rowIndexes = table.selectedRows;
else
rowIndexes = [rowIndex];
let cookies = this._cookiesAtIndexes(rowIndexes);
InspectorFrontendHost.copyText(this._formatCookiesAsText(cookies));
});
if (InspectorBackend.hasCommand("Page.deleteCookie")) {
contextMenu.appendItem(WI.UIString("Delete"), () => {
if (table.isRowSelected(rowIndex))
table.removeSelectedRows();
else
table.removeRow(rowIndex);
});
}
contextMenu.appendSeparator();
}
tableDidRemoveRows(table, rowIndexes)
{
if (!rowIndexes.length)
return;
for (let i = rowIndexes.length - 1; i >= 0; --i) {
let rowIndex = rowIndexes[i];
let cookie = this._filteredCookies[rowIndex];
console.assert(cookie, "Missing cookie for row " + rowIndex);
if (!cookie)
continue;
this._filteredCookies.splice(rowIndex, 1);
this._cookies.remove(cookie);
let target = WI.assumingMainTarget();
target.PageAgent.deleteCookie(cookie.name, cookie.url);
}
}
tablePopulateCell(table, cell, column, rowIndex)
{
let cookie = this._filteredCookies[rowIndex];
cell.textContent = this._formatCookiePropertyForColumn(cookie, column);
if (!this._knownCells.has(cell)) {
this._knownCells.add(cell);
cell.addEventListener("dblclick", (event) => {
if (column.identifier === "size") {
InspectorFrontendHost.beep();
return;
}
this._showCookiePopover(cell, cookie, column.identifier);
});
}
return cell;
}
// Popover delegate
willDismissPopover(popover)
{
if (popover instanceof WI.CookiePopover) {
this._willDismissCookiePopover(popover);
return;
}
console.assert();
}
// Protected
initialLayout()
{
super.initialLayout();
this._table = new WI.Table("cookies-table", this, this, 20);
this._table.allowsMultipleSelection = true;
this._nameColumn = new WI.TableColumn("name", WI.UIString("Name"), {
minWidth: 70,
maxWidth: 300,
initialWidth: 200,
resizeType: WI.TableColumn.ResizeType.Locked,
});
this._valueColumn = new WI.TableColumn("value", WI.UIString("Value"), {
minWidth: 100,
maxWidth: 600,
initialWidth: 200,
hideable: false,
});
this._domainColumn = new WI.TableColumn("domain", WI.unlocalizedString("Domain"), {
minWidth: 100,
maxWidth: 200,
initialWidth: 120,
});
this._pathColumn = new WI.TableColumn("path", WI.unlocalizedString("Path"), {
minWidth: 50,
maxWidth: 300,
initialWidth: 100,
});
this._expiresColumn = new WI.TableColumn("expires", WI.unlocalizedString("Expires"), {
minWidth: 100,
maxWidth: 200,
initialWidth: 150,
});
this._sizeColumn = new WI.TableColumn("size", WI.UIString("Size"), {
minWidth: 50,
maxWidth: 80,
initialWidth: 65,
align: "right",
});
this._secureColumn = new WI.TableColumn("secure", WI.unlocalizedString("Secure"), {
minWidth: 70,
maxWidth: 70,
align: "center",
});
this._httpOnlyColumn = new WI.TableColumn("httpOnly", WI.unlocalizedString("HttpOnly"), {
minWidth: 80,
maxWidth: 80,
align: "center",
});
this._sameSiteColumn = new WI.TableColumn("sameSite", WI.unlocalizedString("SameSite"), {
minWidth: 40,
maxWidth: 80,
initialWidth: 70,
align: "center",
});
this._table.addColumn(this._nameColumn);
this._table.addColumn(this._valueColumn);
this._table.addColumn(this._domainColumn);
this._table.addColumn(this._pathColumn);
this._table.addColumn(this._expiresColumn);
this._table.addColumn(this._sizeColumn);
this._table.addColumn(this._secureColumn);
this._table.addColumn(this._httpOnlyColumn);
this._table.addColumn(this._sameSiteColumn);
if (!this._table.sortColumnIdentifier) {
this._table.sortOrder = WI.Table.SortOrder.Ascending;
this._table.sortColumnIdentifier = "name";
}
this.addSubview(this._table);
this._table.element.addEventListener("keydown", this._handleTableKeyDown.bind(this));
this._reloadCookies();
}
// Private
_getCookiesForHost(cookies, host)
{
let resourceMatchesStorageDomain = (resource) => {
let urlComponents = resource.urlComponents;
return urlComponents && urlComponents.host && urlComponents.host === host;
};
let allResources = [];
for (let frame of WI.networkManager.frames) {
// The main resource isn't in the list of resources, so add it as a candidate.
allResources.push(frame.mainResource, ...frame.resourceCollection);
}
let resourcesForDomain = allResources.filter(resourceMatchesStorageDomain);
let cookiesForDomain = cookies.filter((cookie) => {
return resourcesForDomain.some((resource) => {
return WI.CookieStorageObject.cookieMatchesResourceURL(cookie, resource.url);
});
});
return cookiesForDomain;
}
_generateSortComparator()
{
let sortColumnIdentifier = this._table.sortColumnIdentifier;
if (!sortColumnIdentifier) {
this._sortComparator = null;
return;
}
let comparator = null;
switch (sortColumnIdentifier) {
case "name":
case "value":
case "domain":
case "path":
case "sameSite":
comparator = (a, b) => (a[sortColumnIdentifier] || "").extendedLocaleCompare(b[sortColumnIdentifier] || "");
break;
case "size":
case "httpOnly":
case "secure":
comparator = (a, b) => a[sortColumnIdentifier] - b[sortColumnIdentifier];
break;
case "expires":
comparator = (a, b) => {
if (!a.expires)
return 1;
if (!b.expires)
return -1;
return a.expires - b.expires;
};
break;
default:
console.assert("Unexpected sort column", sortColumnIdentifier);
return;
}
let reverseFactor = this._table.sortOrder === WI.Table.SortOrder.Ascending ? 1 : -1;
this._sortComparator = (a, b) => reverseFactor * comparator(a, b);
}
_showCookiePopover(targetElement, cookie, columnIdentifier) {
console.assert(!this._editingCookie);
this._editingCookie = cookie;
let options = {};
if (columnIdentifier) {
switch (columnIdentifier) {
case "name":
case "value":
case "domain":
case "path":
case "secure":
options.focusField = columnIdentifier;
break;
case "expires":
options.focusField = this._editingCookie.session ? "session" : "expires";
break;
case "httpOnly":
options.focusField = "http-only";
break;
case "sameSite":
options.focusField = "same-site";
break;
default:
console.assert();
break;
}
}
let popover = new WI.CookiePopover(this);
popover.show(this._editingCookie, targetElement, [WI.RectEdge.MAX_Y, WI.RectEdge.MIN_Y, WI.RectEdge.MIN_X, WI.RectEdge.MAX_X], options);
}
async _willDismissCookiePopover(popover)
{
let editingCookie = this._editingCookie;
this._editingCookie = null;
let serializedData = popover.serializedData;
if (!serializedData) {
InspectorFrontendHost.beep();
return;
}
let cookieToSet = WI.Cookie.fromPayload(serializedData);
let cookieProtocolPayload = cookieToSet.toProtocol();
if (!cookieProtocolPayload) {
InspectorFrontendHost.beep();
return;
}
let target = WI.assumingMainTarget();
let promises = [];
if (editingCookie)
promises.push(target.PageAgent.deleteCookie(editingCookie.name, editingCookie.url));
promises.push(target.PageAgent.setCookie(cookieProtocolPayload));
promises.push(this._reloadCookies());
await Promise.all(promises);
let index = this._filteredCookies.findIndex((existingCookie) => cookieToSet.equals(existingCookie));
if (index >= 0)
this._table.selectRow(index);
}
_handleFilterBarFilterDidChange(event)
{
this._updateFilteredCookies();
this._updateEmptyFilterResultsMessage();
this._table.reloadData();
}
_handleSetCookieButtonClick(event)
{
this._showCookiePopover(this._setCookieButtonNavigationItem.element, null, "name");
}
_refreshButtonClicked(event)
{
this._reloadCookies();
}
_handleClearNavigationItemClicked(event)
{
let target = WI.assumingMainTarget();
for (let cookie of this._cookies)
target.PageAgent.deleteCookie(cookie.name, cookie.url);
this._reloadCookies();
}
_reloadCookies()
{
let target = WI.assumingMainTarget();
if (!target.hasCommand("Page.getCookies"))
return;
target.PageAgent.getCookies().then((payload) => {
this._cookies = this._getCookiesForHost(payload.cookies.map(WI.Cookie.fromPayload), this.representedObject.host);
this._updateSort();
this._updateFilteredCookies();
this._updateEmptyFilterResultsMessage();
this._table.reloadData();
}).catch((error) => {
console.error("Could not fetch cookies: ", error);
});
}
_updateSort()
{
if (!this._sortComparator)
return;
this._cookies.sort(this._sortComparator);
}
_updateFilteredCookies()
{
this._filteredCookies = this._cookies;
let filterBar = this._filterBarNavigationItem.filterBar;
filterBar.invalid = false;
let filterText = filterBar.filters.text;
if (!filterText)
return;
let regex = WI.SearchUtilities.filterRegExpForString(filterText, WI.SearchUtilities.defaultSettings);
if (!regex) {
filterBar.invalid = true;
return;
}
this._filteredCookies = this._filteredCookies.filter((cookie) => {
for (let column of this._table.columns) {
let text = this._formatCookiePropertyForColumn(cookie, column);
if (text && regex.test(text))
return true;
}
return false;
});
}
_updateEmptyFilterResultsMessage()
{
if (this._filteredCookies.length || !this._filterBarNavigationItem.filterBar.filters.text) {
if (this._emptyFilterResultsMessageElement)
this._emptyFilterResultsMessageElement.remove();
this._emptyFilterResultsMessageElement = null;
} else {
if (!this._emptyFilterResultsMessageElement) {
let buttonElement = document.createElement("button");
buttonElement.textContent = WI.UIString("Clear Filters");
buttonElement.addEventListener("click", (event) => {
this._filterBarNavigationItem.filterBar.clear();
});
this._emptyFilterResultsMessageElement = WI.createMessageTextView(WI.UIString("No Filter Results"));
this._emptyFilterResultsMessageElement.appendChild(buttonElement);
}
this.element.appendChild(this._emptyFilterResultsMessageElement);
}
}
_handleTableKeyDown(event)
{
if (event.keyCode === WI.KeyboardShortcut.Key.Backspace.keyCode || event.keyCode === WI.KeyboardShortcut.Key.Delete.keyCode) {
if (InspectorBackend.hasCommand("Page.deleteCookie"))
this._table.removeSelectedRows();
else
InspectorFrontendHost.beep();
}
}
_cookiesAtIndexes(indexes)
{
if (!this._filteredCookies.length)
return [];
return indexes.map((index) => this._filteredCookies[index]);
}
_formatCookiesAsText(cookies)
{
let visibleColumns = this._table.columns.filter((column) => !column.hidden);
if (!visibleColumns.length)
return "";
let lines = cookies.map((cookie) => {
const usePunctuation = false;
let values = visibleColumns.map((column) => this._formatCookiePropertyForColumn(cookie, column, usePunctuation));
return values.join("\t");
});
return lines.join("\n");
}
_formatCookiePropertyForColumn(cookie, column, usePunctuation = true)
{
const checkmark = "\u2713";
const missingValue = usePunctuation ? emDash : "";
const missingCheckmark = usePunctuation ? zeroWidthSpace : "";
switch (column.identifier) {
case "name":
return cookie.name;
case "value":
return cookie.value;
case "domain":
return cookie.domain || missingValue;
case "path":
return cookie.path || missingValue;
case "expires":
return (!cookie.session && cookie.expires) ? cookie.expires.toLocaleString() : WI.UIString("Session");
case "size":
return Number.bytesToString(cookie.size);
case "secure":
return cookie.secure ? checkmark : missingCheckmark;
case "httpOnly":
return cookie.httpOnly ? checkmark : missingCheckmark;
case "sameSite":
return cookie.sameSite === WI.Cookie.SameSiteType.None ? missingValue : WI.Cookie.displayNameForSameSiteType(cookie.sameSite);
}
console.assert("Unexpected table column " + column.identifier);
return "";
}
};
WI.CookieType = {
Request: 0,
Response: 1
};