-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathformcheck.js
More file actions
117 lines (104 loc) · 5.1 KB
/
formcheck.js
File metadata and controls
117 lines (104 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// content script -- form data check + tab title marking
// Guard against double-injection (service worker restart re-injects)
if (!window._drowzyFormcheckLoaded) {
window._drowzyFormcheckLoaded = true;
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.action === 'checkFormData') {
sendResponse({ hasFormData: hasUnsavedFormData() });
} else if (msg.action === 'markSuspended') {
const pfx = chrome.i18n.getMessage('markSuspendedPrefix') || '[zzz] ';
if (!document.title.startsWith(pfx)) {
document.title = pfx + document.title;
}
sendResponse({ success: true });
} else if (msg.action === 'unmarkSuspended') {
const pfx = chrome.i18n.getMessage('markSuspendedPrefix') || '[zzz] ';
if (document.title.startsWith(pfx)) {
document.title = document.title.substring(pfx.length);
}
sendResponse({ success: true });
} else if (msg.action === 'suspendWarning') {
showSuspendWarning();
sendResponse({ success: true });
}
});
function showSuspendWarning() {
// a banner on a hidden tab is theater - the user can't see it. only render
// when the tab is actually visible. when hidden, the suspend goes through
// silently (which the user already wasn't paying attention to).
if (document.visibilityState !== 'visible') return;
if (!document.body || document.getElementById('drowzy-suspend-warning')) return;
var el = document.createElement('div');
el.id = 'drowzy-suspend-warning';
el.setAttribute('role', 'alert');
el.style.cssText = 'position:fixed;top:0;left:0;right:0;padding:8px 14px;background:rgba(167,139,250,0.97);color:#fff;font:13px -apple-system,system-ui,sans-serif;text-align:center;z-index:2147483647;transition:opacity 0.3s;display:flex;align-items:center;justify-content:center;gap:12px;box-shadow:0 2px 8px rgba(0,0,0,0.15);';
var bannerText = document.createElement('span');
bannerText.textContent = chrome.i18n.getMessage('suspendWarningText') || 'Suspending tab soon...';
bannerText.style.cssText = 'cursor:pointer;';
bannerText.title = chrome.i18n.getMessage('clickToDismiss') || 'Click to dismiss';
bannerText.addEventListener('click', function() { if (el.parentNode) el.remove(); });
var keepBtn = document.createElement('button');
keepBtn.textContent = chrome.i18n.getMessage('keepAwakeBtn') || 'Keep awake';
keepBtn.style.cssText = 'background:#fff;color:#7c3aed;border:none;padding:4px 12px;border-radius:4px;font:600 12px -apple-system,system-ui,sans-serif;cursor:pointer;';
keepBtn.addEventListener('click', function(e) {
e.stopPropagation();
// tells background to bump this tab's timestamp + skip the imminent discard
try { chrome.runtime.sendMessage({ action: 'keepTabAwake' }); } catch {}
if (el.parentNode) el.remove();
});
el.appendChild(bannerText);
el.appendChild(keepBtn);
document.body.appendChild(el);
// background discards ~2500ms after warning, so the banner lifetime is bounded
// by the tab itself unloading. fade slightly before discard so it's not jarring.
setTimeout(function() { el.style.opacity = '0'; }, 2200);
setTimeout(function() { if (el.parentNode) el.remove(); }, 2700);
}
var _ceSnapshots = null;
function snapshotContentEditables() {
if (_ceSnapshots) return;
_ceSnapshots = new WeakMap();
var editables = document.querySelectorAll('[contenteditable="true"]');
for (var i = 0; i < editables.length; i++) {
_ceSnapshots.set(editables[i], editables[i].textContent);
}
}
// Snapshot on load so we can compare later
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', snapshotContentEditables);
} else {
snapshotContentEditables();
}
function hasUnsavedFormData() {
var fields = document.querySelectorAll(
'input[type="text"], input[type="email"], ' +
'input[type="url"], input[type="tel"], input[type="password"], ' +
'input[type="number"], input[type="search"], ' +
'input[type="date"], input[type="datetime-local"], ' +
'input[type="month"], input[type="time"], input[type="week"], ' +
'input[type="color"], input:not([type]), textarea, [contenteditable="true"]'
);
for (var i = 0; i < fields.length; i++) {
var el = fields[i];
if (el.isContentEditable) {
var original = _ceSnapshots ? _ceSnapshots.get(el) : undefined;
if (original === undefined) {
// Element added after snapshot — treat as dirty if non-empty
if (el.textContent.trim()) return true;
} else if (el.textContent !== original) {
return true;
}
} else if (el.value !== el.defaultValue) {
return true;
}
}
// also check for changed checkboxes/radio buttons (e.g. someone toggled a
// settings page and didn't save). a discarded radio/checkbox state would be
// just as lost as a typed input.
var toggles = document.querySelectorAll('input[type="checkbox"], input[type="radio"]');
for (var j = 0; j < toggles.length; j++) {
if (toggles[j].checked !== toggles[j].defaultChecked) return true;
}
return false;
}
} // end double-injection guard