284 lines
10 KiB
JavaScript
284 lines
10 KiB
JavaScript
/*
|
|
* Copyright (C) 2020 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.CookiePopover = class CookiePopover extends WI.Popover
|
|
{
|
|
constructor(delegate)
|
|
{
|
|
super(delegate);
|
|
|
|
this._nameInputElement = null;
|
|
this._valueInputElement = null;
|
|
this._domainInputElement = null;
|
|
this._pathInputElement = null;
|
|
this._sessionCheckboxElement = null;
|
|
this._expiresInputElement = null;
|
|
this._httpOnlyCheckboxElement = null;
|
|
this._secureCheckboxElement = null;
|
|
this._sameSiteSelectElement = null;
|
|
|
|
this._serializedDataWhenShown = null;
|
|
|
|
this.windowResizeHandler = this._presentOverTargetElement.bind(this);
|
|
}
|
|
|
|
// Public
|
|
|
|
get serializedData()
|
|
{
|
|
if (!this._targetElement)
|
|
return null;
|
|
|
|
let name = this._nameInputElement.value;
|
|
if (!name)
|
|
return null;
|
|
|
|
let domain = this._domainInputElement.value || this._domainInputElement.placeholder;
|
|
if (!domain)
|
|
return null;
|
|
|
|
let path = this._pathInputElement.value || this._pathInputElement.placeholder;
|
|
if (!path)
|
|
return null;
|
|
|
|
let session = this._sessionCheckboxElement.checked;
|
|
let expires = this._parseExpires();
|
|
if (!session && isNaN(expires))
|
|
return null;
|
|
|
|
// If a full URL is entered in the domain input, parse it to get just the domain.
|
|
try {
|
|
let url = new URL(domain);
|
|
domain = url.hostname;
|
|
} catch { }
|
|
|
|
if (!path.startsWith("/"))
|
|
path = "/" + path;
|
|
|
|
let data = {
|
|
name,
|
|
value: this._valueInputElement.value,
|
|
domain,
|
|
path,
|
|
httpOnly: this._httpOnlyCheckboxElement.checked,
|
|
secure: this._secureCheckboxElement.checked,
|
|
sameSite: this._sameSiteSelectElement.value,
|
|
};
|
|
|
|
if (session)
|
|
data.session = true;
|
|
else
|
|
data.expires = expires;
|
|
|
|
if (JSON.stringify(data) === JSON.stringify(this._serializedDataWhenShown))
|
|
return null;
|
|
|
|
return data;
|
|
}
|
|
|
|
show(cookie, targetElement, preferredEdges, options = {})
|
|
{
|
|
console.assert(!cookie || cookie instanceof WI.Cookie, cookie);
|
|
console.assert(targetElement instanceof Element, targetElement);
|
|
console.assert(Array.isArray(preferredEdges), preferredEdges);
|
|
|
|
this._targetElement = targetElement;
|
|
this._preferredEdges = preferredEdges;
|
|
|
|
function formatDate(date) {
|
|
function pad(number) {
|
|
return String(number).padStart(2, "0");
|
|
}
|
|
return [
|
|
date.getFullYear(),
|
|
"-",
|
|
pad(date.getMonth() + 1),
|
|
"-",
|
|
pad(date.getDate()),
|
|
"T",
|
|
pad(date.getHours()),
|
|
":",
|
|
pad(date.getMinutes()),
|
|
":",
|
|
pad(date.getSeconds()),
|
|
].join("");
|
|
}
|
|
|
|
let data = {};
|
|
if (cookie) {
|
|
data.name = cookie.name;
|
|
data.value = cookie.value;
|
|
data.domain = cookie.domain;
|
|
data.path = cookie.path;
|
|
data.expires = formatDate(cookie.expires || this._defaultExpires());
|
|
data.session = cookie.session;
|
|
data.httpOnly = cookie.httpOnly;
|
|
data.secure = cookie.secure;
|
|
data.sameSite = cookie.sameSite;
|
|
} else {
|
|
let urlComponents = WI.networkManager.mainFrame.mainResource.urlComponents;
|
|
data.name = "";
|
|
data.value = "";
|
|
data.domain = urlComponents.host;
|
|
data.path = urlComponents.path;
|
|
data.expires = formatDate(this._defaultExpires());
|
|
data.session = true;
|
|
data.httpOnly = false;
|
|
data.secure = false;
|
|
data.sameSite = WI.Cookie.SameSiteType.None;
|
|
}
|
|
|
|
let popoverContentElement = document.createElement("div");
|
|
popoverContentElement.className = "cookie-popover-content";
|
|
|
|
let tableElement = popoverContentElement.appendChild(document.createElement("table"));
|
|
|
|
function createRow(id, label, editorElement) {
|
|
let domId = `cookie-popover-${id}-editor`;
|
|
|
|
let rowElement = tableElement.appendChild(document.createElement("tr"));
|
|
|
|
let headerElement = rowElement.appendChild(document.createElement("th"));
|
|
|
|
let labelElement = headerElement.appendChild(document.createElement("label"));
|
|
labelElement.setAttribute("for", domId);
|
|
labelElement.textContent = label;
|
|
|
|
let dataElement = rowElement.appendChild(document.createElement("td"));
|
|
|
|
editorElement.id = domId;
|
|
dataElement.appendChild(editorElement);
|
|
|
|
if (id === options.focusField) {
|
|
setTimeout(() => {
|
|
editorElement.focus();
|
|
editorElement.select?.();
|
|
});
|
|
}
|
|
|
|
return {rowElement};
|
|
}
|
|
|
|
let boundHandleInputKeyDown = this._handleInputKeyDown.bind(this);
|
|
|
|
function createInputRow(id, label, type, value) {
|
|
let inputElement = document.createElement("input");
|
|
inputElement.type = type;
|
|
|
|
if (type === "checkbox")
|
|
inputElement.checked = value;
|
|
else {
|
|
if (cookie)
|
|
inputElement.value = value;
|
|
inputElement.placeholder = value;
|
|
inputElement.spellcheck = false;
|
|
inputElement.addEventListener("keydown", boundHandleInputKeyDown);
|
|
}
|
|
|
|
let rowElement = createRow(id, label, inputElement).rowElement;
|
|
|
|
return {inputElement, rowElement};
|
|
}
|
|
|
|
this._nameInputElement = createInputRow("name", WI.UIString("Name"), "text", data.name).inputElement;
|
|
this._nameInputElement.required = true;
|
|
|
|
this._valueInputElement = createInputRow("value", WI.UIString("Value"), "text", data.value).inputElement;
|
|
|
|
this._domainInputElement = createInputRow("domain", WI.unlocalizedString("Domain"), "text", data.domain).inputElement;
|
|
|
|
this._pathInputElement = createInputRow("path", WI.unlocalizedString("Path"), "text", data.path).inputElement;
|
|
|
|
this._sessionCheckboxElement = createInputRow("session", WI.unlocalizedString("Session"), "checkbox", data.session).inputElement;
|
|
|
|
let expiresInputRow = createInputRow("expires", WI.unlocalizedString("Expires"), "datetime-local", data.expires);
|
|
this._expiresInputElement = expiresInputRow.inputElement;
|
|
this._expiresInputElement.step = 1; // Causes the seconds field to be shown.
|
|
this._expiresInputElement.addEventListener("input", (event) => {
|
|
this._expiresInputElement.classList.toggle("invalid", isNaN(this._parseExpires()));
|
|
});
|
|
|
|
this._httpOnlyCheckboxElement = createInputRow("http-only", WI.unlocalizedString("HttpOnly"), "checkbox", data.httpOnly).inputElement;
|
|
|
|
this._secureCheckboxElement = createInputRow("secure", WI.unlocalizedString("Secure"), "checkbox", data.secure).inputElement;
|
|
|
|
this._sameSiteSelectElement = document.createElement("select");
|
|
for (let sameSiteType of Object.values(WI.Cookie.SameSiteType)) {
|
|
let optionElement = this._sameSiteSelectElement.appendChild(document.createElement("option"));
|
|
optionElement.value = sameSiteType;
|
|
optionElement.textContent = WI.Cookie.displayNameForSameSiteType(sameSiteType);
|
|
}
|
|
this._sameSiteSelectElement.value = data.sameSite;
|
|
createRow("same-site", WI.unlocalizedString("SameSite"), this._sameSiteSelectElement);
|
|
|
|
let toggleExpiresRow = () => {
|
|
expiresInputRow.rowElement.hidden = this._sessionCheckboxElement.checked;
|
|
|
|
this.update();
|
|
};
|
|
|
|
this._sessionCheckboxElement.addEventListener("change", (event) => {
|
|
toggleExpiresRow();
|
|
});
|
|
|
|
toggleExpiresRow();
|
|
|
|
this._serializedDataWhenShown = this.serializedData;
|
|
|
|
this.content = popoverContentElement;
|
|
this._presentOverTargetElement();
|
|
}
|
|
|
|
// Private
|
|
|
|
_presentOverTargetElement()
|
|
{
|
|
if (!this._targetElement)
|
|
return;
|
|
|
|
let targetFrame = WI.Rect.rectFromClientRect(this._targetElement.getBoundingClientRect());
|
|
this.present(targetFrame.pad(2), this._preferredEdges);
|
|
}
|
|
|
|
_defaultExpires()
|
|
{
|
|
return new Date(Date.now() + (1000 * 60 * 60 * 24)); // one day in the future
|
|
}
|
|
|
|
_parseExpires()
|
|
{
|
|
let timestamp = Date.parse(this._expiresInputElement.value || this._expiresInputElement.placeholder);
|
|
if (timestamp < Date.now())
|
|
return NaN;
|
|
return timestamp;
|
|
}
|
|
|
|
_handleInputKeyDown(event)
|
|
{
|
|
if (event.key === "Enter" || event.key === "Esc")
|
|
this.dismiss();
|
|
}
|
|
};
|