Full upgrade: Uses Shadow DOM for invisibility, stable drag, and robust deletion.
// ==UserScript==
// @name Cookie Manager (Shadow DOM & Stable Deletion) - FINAL
// @namespace http://tampermonkey.net/
// @version 2.0
// @description Full upgrade: Uses Shadow DOM for invisibility, stable drag, and robust deletion.
// @author Balta zar
// @match *://*/*
// @grant none
// @run-at document-idle
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// Use document-idle for Tampermonkey header, but keep the domReady fallback
function domReady(cb) {
if (/complete|interactive/.test(document.readyState)) cb();
else document.addEventListener("DOMContentLoaded", cb);
}
// Wrap the entire logic in a small delay to defeat aggressive SPAs
setTimeout(() => {
domReady(() => {
class CookieManager {
constructor() {
this.currentDomain = location.hostname;
this.cookies = [];
this.filtered = [];
this.shadowRoot = null; // Will store the Shadow Root
}
init() {
this.loadCookies();
this.buildUI();
this.bindEvents();
}
loadCookies() {
this.cookies = [];
const raw = document.cookie;
if (!raw) {
this.filtered = [];
return;
}
raw.split(";").forEach(pair => {
const parts = pair.trim().split("=");
const name = parts[0];
const value = parts.slice(1).join("=");
if (!name) return;
this.cookies.push({
name: decodeURIComponent(name),
value: decodeURIComponent(value),
domain: location.hostname,
path: "/"
});
});
this.filtered = [...this.cookies];
}
/**
* Builds the UI inside a Shadow DOM for isolation.
*/
buildUI() {
if (document.getElementById("cookieManagerHost")) return;
/* 1. Create the Shadow Host and attach the Shadow Root */
const host = document.createElement("div");
host.id = "cookieManagerHost";
document.body.appendChild(host);
// This is the 'illusion' layer!
this.shadowRoot = host.attachShadow({ mode: 'open' });
/* Styles (injected into Shadow Root, isolated from website CSS) */
const style = document.createElement("style");
style.textContent = `
/* :host sets the style for the container element itself */
:host {
all: initial; /* Essential for isolation */
}
.cook-btn-main {
position: fixed; top: 20px; right: 20px;
background: rgba(103,58,183,0.9);
color: #fff;
border: none;
border-radius: 50%;
width: 60px; height: 60px;
cursor: pointer;
font-size: 26px;
z-index: 999999;
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
line-height: 60px;
text-align: center;
}
.cookie-panel {
position: fixed;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 900px;
background: rgba(20,20,30,0.95);
color: white;
border-radius: 18px;
padding: 20px;
display: none;
z-index: 9999999;
font-family: sans-serif;
box-sizing: border-box;
}
.cookie-header {
padding: 10px;
margin-bottom: 10px;
background: rgba(255,255,255,0.1);
border-radius: 10px;
cursor: move;
user-select: none;
}
table { width: 100%; margin-top: 15px; border-collapse: collapse; table-layout: fixed; }
th, td { padding: 8px 10px; border-bottom: 1px solid rgba(255,255,255,0.2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
tr:hover { background: rgba(255,255,255,0.07); }
.cbtn {
padding: 7px 10px;
border: none;
border-radius: 6px;
background: rgba(255,255,255,0.15);
color: #fff;
cursor: pointer;
margin-left: 5px;
font-size: 14px;
}
.cbtn:hover { background: rgba(255,255,255,0.25); }
.c-input {
width: 100%;
margin-bottom: 10px;
padding: 8px;
border-radius: 6px;
border: 1px solid rgba(255,255,255,0.3);
background: rgba(255,255,255,0.12);
color: #fff;
box-sizing: border-box;
}
td:nth-child(3) { width: 90px; text-align: right; }
td:nth-child(1) { width: 30%; }
td:nth-child(2) { width: auto; }
`;
this.shadowRoot.appendChild(style);
/* Toggle Button */
const toggle = document.createElement("button");
toggle.id = "cookieToggleBtn";
toggle.className = "cook-btn-main";
toggle.textContent = "🍪";
this.shadowRoot.appendChild(toggle);
/* Panel */
const panel = document.createElement("div");
panel.id = "cookiePanel";
panel.className = "cookie-panel";
panel.innerHTML = `
<div id="cookieHeader" class="cookie-header"><h2 style="margin:0;">Cookie Manager</h2></div>
<input id="cookieSearch" class="c-input" placeholder="Search cookies...">
<button id="addCookie" class="cbtn">Add</button>
<button id="refreshCookie" class="cbtn">Refresh</button>
<button id="deleteAllCookie" class="cbtn" style="background:rgba(255,50,50,0.5);">Delete All</button>
<table>
<thead><tr><th>Name</th><th>Value</th><th>Actions</th></tr></thead>
<tbody id="cookieList"></tbody>
</table>
`;
this.shadowRoot.appendChild(panel);
// Get references from the Shadow Root
this.panel = this.shadowRoot.getElementById("cookiePanel");
this.header = this.shadowRoot.getElementById("cookieHeader");
this.dragInit = false;
this.enableDrag();
this.render();
}
/* Drag System - FIXED */
enableDrag() {
let dragging = false, offsetX = 0, offsetY = 0;
let currentX = 0, currentY = 0;
const start = (e) => {
// Prevent text selection during drag
e.preventDefault();
dragging = true;
if (!this.dragInit) {
// On first drag, calculate initial position from transform
const r = this.panel.getBoundingClientRect();
currentX = r.left;
currentY = r.top;
this.panel.style.transform = "none";
this.panel.style.left = currentX + "px";
this.panel.style.top = currentY + "px";
this.dragInit = true;
}
// Calculate offset from the mouse click to the panel's current position
offsetX = e.clientX - parseFloat(this.panel.style.left);
offsetY = e.clientY - parseFloat(this.panel.style.top);
this.panel.style.cursor = 'grabbing';
};
const move = (e) => {
if (!dragging) return;
// Clamp the movement to avoid dragging off-screen
currentX = Math.max(0, e.clientX - offsetX);
currentY = Math.max(0, e.clientY - offsetY);
this.panel.style.left = currentX + "px";
this.panel.style.top = currentY + "px";
};
const end = () => {
dragging = false;
this.panel.style.cursor = 'move';
};
this.header.addEventListener("mousedown", start);
// Must attach mouse events to the main document for reliable drag outside the panel
document.addEventListener("mousemove", move);
document.addEventListener("mouseup", end);
}
bindEvents() {
// All elements are now retrieved from the Shadow Root
this.shadowRoot.getElementById("cookieToggleBtn").onclick = () => {
this.panel.style.display =
this.panel.style.display === "block" ? "none" : "block";
if (this.panel.style.display === "block") {
this.refresh();
}
};
this.shadowRoot.getElementById("addCookie").onclick = () => this.addPrompt();
this.shadowRoot.getElementById("refreshCookie").onclick = () => this.refresh();
this.shadowRoot.getElementById("deleteAllCookie").onclick = () => this.deleteAll();
this.shadowRoot.getElementById("cookieSearch").oninput = () => this.search();
}
refresh() {
this.loadCookies();
this.render();
}
search() {
const q = this.shadowRoot.getElementById("cookieSearch").value.toLowerCase();
this.filtered = this.cookies.filter(c =>
c.name.toLowerCase().includes(q) ||
c.value.toLowerCase().includes(q)
);
this.render();
}
render() {
const body = this.shadowRoot.getElementById("cookieList");
if (!body) return;
body.innerHTML = "";
if (this.filtered.length === 0) {
body.innerHTML = `<tr><td colspan="3" style="text-align:center;">No cookies found.</td></tr>`;
return;
}
this.filtered.forEach((c) => {
const tr = document.createElement("tr");
const escapedName = this.escape(c.name);
const escapedValue = this.escape(c.value);
tr.innerHTML = `
<td>${escapedName}</td>
<td>${escapedValue.slice(0, 40)}${c.value.length > 40 ? "…" : ""}</td>
<td>
<button class="cbtn" data-action="edit" data-name="${escapedName}">✎</button>
<button class="cbtn" data-action="del" data-name="${escapedName}">×</button>
</td>
`;
body.appendChild(tr);
});
// Attach event listeners using the Shadow Root context
body.querySelectorAll("button").forEach(btn => {
const name = btn.dataset.name;
if (!name) return;
// Unescape the name before passing it to the action methods
const unescapedName = name.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'");
if (btn.dataset.action === "del") btn.onclick = () => this.delete(unescapedName);
if (btn.dataset.action === "edit") btn.onclick = () => this.editPrompt(unescapedName);
});
}
addPrompt() {
const n = prompt("Cookie name:");
if (!n) return;
const v = prompt("Cookie value:") || "";
document.cookie = `${encodeURIComponent(n)}=${encodeURIComponent(v)}; path=/; secure=${location.protocol === 'https:'}`;
this.refresh();
}
editPrompt(name) {
const cookie = this.cookies.find(c => c.name === name);
const v = prompt(`New value for '${name}':`, cookie?.value ?? "");
if (v === null) return;
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(v)}; path=/; secure=${location.protocol === 'https:'}`;
this.refresh();
}
/**
* Deletes a cookie by trying common domain and path variations. - FIXED
*/
delete(name) {
const paths = [
location.pathname.substring(0, location.pathname.lastIndexOf('/') + 1),
'/'
];
// Try the current domain and the superdomain
const domains = [
location.hostname,
`.${location.hostname.split('.').slice(-2).join('.')}` // e.g. .example.com
];
const expires = 'Thu, 01 Jan 1970 00:00:00 GMT';
domains.forEach(d => {
paths.forEach(p => {
document.cookie =
`${encodeURIComponent(name)}=; domain=${d}; path=${p}; expires=${expires}`;
});
});
this.refresh();
}
deleteAll() {
if (!confirm(`Are you sure you want to delete ALL ${this.cookies.length} visible cookies? This cannot be undone.`)) {
return;
}
[...this.cookies].forEach(c => this.delete(c.name));
}
/**
* Escape HTML entities to prevent Cross-Site Scripting (XSS).
*/
escape(str) {
if (typeof str !== 'string') return '';
return str.replace(/[&<>"']/g, m => ({
"&":"&", "<":"<", ">":">", '"':""", "'":"'"
}[m]));
}
}
const cm = new CookieManager();
cm.init();
});
}, 500); // Small 500ms delay to help against aggressive SPAs
})();