| /* |
| * 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(); |
| } |
| }; |