// ==UserScript==
// @name Force Native Browser Find (Restore Ctrl+F Search)
// @name:ar فرض البحث الأصلي للمتصفح (استعادة Ctrl+F)
// @name:es Forzar Búsqueda Nativa del Navegador (Restaurar Ctrl+F)
// @name:fr Forcer la Recherche Native du Navigateur (Restaurer Ctrl+F)
// @name:hi मूल ब्राउज़र खोज को सक्षम करें (Ctrl+F को पुनर्स्थापित करें)
// @name:id Paksa Pencarian Asli Browser (Kembalikan Ctrl+F)
// @name:ja ブラウザ標準検索を強制使用(Ctrl+F復元)
// @name:ko 브라우저 기본 검색 강제 사용 (Ctrl+F 복원)
// @name:nl Forceer Standaard Browserzoeken (Herstel Ctrl+F)
// @name:pt-BR Forçar Busca Nativa do Navegador (Restaurar Ctrl+F)
// @name:ru Принудительный Поиск Браузера (Восстановить Ctrl+F)
// @name:vi Bắt Buộc Tìm Kiếm Gốc Trình Duyệt (Khôi Phục Ctrl+F)
// @name:zh-CN 强制浏览器原生搜索(恢复 Ctrl+F)
// @name:zh-TW 強制瀏覽器原生搜尋(恢復 Ctrl+F)
// @description Prevents websites from overriding the browser's built-in Ctrl+F search functionality.
// @description:ar يمنع المواقع من تجاوز وظيفة البحث المدمجة Ctrl+F في المتصفح.
// @description:es Evita que los sitios web anulen la función de búsqueda Ctrl+F integrada del navegador.
// @description:fr Empêche les sites web de remplacer la fonction de recherche Ctrl+F intégrée du navigateur.
// @description:hi वेबसाइटों को ब्राउज़र के अंतर्निहित Ctrl+F खोज कार्यक्षमता को बदलने से रोकता है।
// @description:id Mencegah situs web mengganti fungsi pencarian Ctrl+F bawaan browser.
// @description:ja ウェブサイトがブラウザ内蔵のCtrl+F検索機能を無効化することを防ぎます。
// @description:ko 웹사이트가 브라우저의 기본 Ctrl+F 검색 기능을 무력화하는 것을 방지합니다.
// @description:nl Voorkomt dat websites de ingebouwde Ctrl+F zoekfunctie van de browser uitschakelen.
// @description:pt-BR Impede que sites desabilitem a função de busca Ctrl+F integrada do navegador.
// @description:ru Предотвращает отключение встроенной функции поиска Ctrl+F браузера сайтами.
// @description:vi Ngăn các trang web vô hiệu hóa chức năng tìm kiếm Ctrl+F tích hợp của trình duyệt.
// @description:zh-CN 防止网站覆盖浏览器内置的 Ctrl+F 搜索功能。
// @description:zh-TW 防止網站覆蓋瀏覽器內建的 Ctrl+F 搜尋功能。
// @namespace Bigrand
// @version 1.0.0
// @match *://*/*
// @license MIT
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// CONFIGURATION
const CONFIG = {
shortcuts: {
toggleUI: GM_getValue('shortcut_ui', 'ctrl+shift+s'),
quickToggle: GM_getValue('shortcut_toggle', 'ctrl+shift+d')
},
keys: {
SITE_PROTECTION: `ctrlf_${location.hostname}`,
GLOBAL_PROTECTION: 'ctrlf_global',
SHOW_UI: 'ctrlf_show_ui'
},
ui: {
showDelay: 500,
notificationDuration: 3000,
shortcutRecordTimeout: 5000
}
};
// STATE MANAGEMENT
const state = {
global: GM_getValue(CONFIG.keys.GLOBAL_PROTECTION, true),
site: GM_getValue(CONFIG.keys.SITE_PROTECTION, null),
showUI: GM_getValue(CONFIG.keys.SHOW_UI, true),
uiVisible: false,
recordingShortcut: null,
panel: null,
cleanupFunctions: new Set()
};
// CORE PROTECTION LOGIC
const protection = {
isEnabled() {
return state.site !== null ? state.site : state.global;
},
getSource() {
return state.site !== null ? 'site' : 'global';
},
toggle() {
if (state.site !== null) {
state.site = !state.site;
GM_setValue(CONFIG.keys.SITE_PROTECTION, state.site);
} else {
state.global = !state.global;
GM_setValue(CONFIG.keys.GLOBAL_PROTECTION, state.global);
}
ui.update();
utils.showNotification(
`Protection ${this.isEnabled() ? 'ON' : 'OFF'} ${this.getSource() === 'site' ? 'for this site' : 'globally'}`
);
},
resetSite() {
state.site = null;
GM_setValue(CONFIG.keys.SITE_PROTECTION, null);
ui.update();
}
};
// KEYBOARD HANDLING
const keyboard = {
matchShortcut(e, shortcut) {
const parts = shortcut.split('+');
const key = parts.pop();
return e.ctrlKey === parts.includes('ctrl') &&
e.shiftKey === parts.includes('shift') &&
e.altKey === parts.includes('alt') &&
!e.metaKey &&
e.key.toLowerCase() === key;
},
handleKeydown(e) {
if (state.recordingShortcut) return;
if (this.matchShortcut(e, CONFIG.shortcuts.toggleUI)) {
e.preventDefault();
e.stopImmediatePropagation();
ui.toggle();
return;
}
if (this.matchShortcut(e, CONFIG.shortcuts.quickToggle)) {
e.preventDefault();
e.stopImmediatePropagation();
protection.toggle();
return;
}
// Protect Ctrl+F
if (e.ctrlKey && !e.altKey && !e.metaKey && e.key.toLowerCase() === 'f') {
if (protection.isEnabled()) {
e.stopImmediatePropagation();
}
}
},
recordShortcut(btn) {
if (state.recordingShortcut) return;
const type = btn.dataset.type;
const originalDisplay = btn.querySelector('.ctrlf-shortcut-display').textContent;
state.recordingShortcut = type;
btn.classList.add('recording');
btn.querySelector('.ctrlf-shortcut-hint').textContent = 'Press keys...';
const handleKeyRecord = (e) => {
e.preventDefault();
e.stopPropagation();
// Ignore modifier-only keys
if (['Control', 'Shift', 'Alt', 'Meta'].includes(e.key)) return;
const parts = [];
if (e.ctrlKey) parts.push('ctrl');
if (e.shiftKey) parts.push('shift');
if (e.altKey) parts.push('alt');
// Validate key input
const key = e.key.toLowerCase();
if (key.length === 1 && /[a-z0-9]/.test(key)) {
parts.push(key);
} else if (['escape', 'enter', 'space', 'tab'].includes(key)) {
parts.push(key);
} else {
utils.showNotification('Invalid key combination');
cleanup();
return;
}
const newShortcut = parts.join('+');
if (parts.length >= 2) {
// Check for conflicts
const existingShortcuts = Object.values(CONFIG.shortcuts);
if (existingShortcuts.includes(newShortcut) && CONFIG.shortcuts[type] !== newShortcut) {
utils.showNotification('Shortcut already in use');
cleanup();
return;
}
CONFIG.shortcuts[type] = newShortcut;
GM_setValue(`shortcut_${type === 'toggleUI' ? 'ui' : 'toggle'}`, newShortcut);
btn.querySelector('.ctrlf-shortcut-display').textContent = newShortcut;
utils.showNotification(`Shortcut updated: ${newShortcut}`);
} else {
utils.showNotification('Shortcut must include modifier key');
}
cleanup();
};
let cleanup = () => {
btn.classList.remove('recording');
btn.querySelector('.ctrlf-shortcut-hint').textContent = 'Click to change';
document.removeEventListener('keydown', handleKeyRecord, true);
state.recordingShortcut = null;
};
// Handle escape to cancel
const handleEscape = (e) => {
if (e.key === 'Escape') {
utils.showNotification('Shortcut recording cancelled');
cleanup();
}
};
document.addEventListener('keydown', handleKeyRecord, true);
document.addEventListener('keydown', handleEscape);
// Auto-cleanup after timeout
const timeoutId = setTimeout(() => {
utils.showNotification('Shortcut recording timed out');
cleanup();
}, CONFIG.ui.shortcutRecordTimeout);
cleanup = () => {
clearTimeout(timeoutId);
document.removeEventListener('keydown', handleEscape);
cleanup();
};
}
};
// UI MANAGEMENT
const ui = {
toggle() {
state.uiVisible ? this.hide() : this.show();
},
show() {
if (state.panel) return;
state.uiVisible = true;
state.panel = utils.createElement('div', 'ctrlf-panel', this.getHTML());
utils.addStyles();
const cleanup = utils.makeDraggable(state.panel, state.panel.querySelector('.ctrlf-header'));
state.cleanupFunctions.add(cleanup);
this.setupEventListeners();
document.body.appendChild(state.panel);
this.update();
},
hide() {
if (state.panel) {
// Cleanup all event listeners
state.cleanupFunctions.forEach(cleanup => cleanup());
state.cleanupFunctions.clear();
state.panel.remove();
state.panel = null;
state.uiVisible = false;
}
},
getHTML() {
return `
<div class="ctrlf-header">
<span class="ctrlf-title">Ctrl+F Protection</span>
<button class="ctrlf-close" title="Close (${CONFIG.shortcuts.toggleUI})">×</button>
</div>
<div class="ctrlf-content">
<div class="ctrlf-status">
<div class="ctrlf-status-indicator"></div>
<span class="ctrlf-status-text"></span>
</div>
<div class="ctrlf-section">
<div class="ctrlf-control-group">
<label class="ctrlf-toggle-label">
<input type="checkbox" class="ctrlf-toggle" id="globalToggle">
<span class="ctrlf-toggle-slider"></span>
<span class="ctrlf-toggle-text">Global Protection</span>
</label>
<small class="ctrlf-control-desc">Default protection for all websites</small>
</div>
<div class="ctrlf-control-group">
<label class="ctrlf-toggle-label">
<input type="checkbox" class="ctrlf-toggle" id="siteToggle">
<span class="ctrlf-toggle-slider"></span>
<span class="ctrlf-toggle-text">Override for ${location.hostname}</span>
</label>
<small class="ctrlf-control-desc">Site-specific override (takes priority)</small>
</div>
<button class="ctrlf-reset-btn" id="resetSite">Reset to Global</button>
</div>
<div class="ctrlf-section">
<h4>Settings</h4>
<div class="ctrlf-control-group">
<label class="ctrlf-toggle-label">
<input type="checkbox" class="ctrlf-toggle" id="showUIToggle">
<span class="ctrlf-toggle-slider"></span>
<span class="ctrlf-toggle-text">Show UI on page load</span>
</label>
</div>
</div>
<div class="ctrlf-section">
<h4>Shortcuts</h4>
<div class="ctrlf-shortcut-item">
<label>Toggle UI:</label>
<button class="ctrlf-shortcut-btn" data-type="toggleUI">
<span class="ctrlf-shortcut-display">${CONFIG.shortcuts.toggleUI}</span>
<span class="ctrlf-shortcut-hint">Click to change</span>
</button>
</div>
<div class="ctrlf-shortcut-item">
<label>Quick toggle:</label>
<button class="ctrlf-shortcut-btn" data-type="quickToggle">
<span class="ctrlf-shortcut-display">${CONFIG.shortcuts.quickToggle}</span>
<span class="ctrlf-shortcut-hint">Click to change</span>
</button>
</div>
</div>
</div>
`;
},
update() {
if (!state.panel) return;
const isEnabled = protection.isEnabled();
const source = protection.getSource();
// Status indicator and text
const indicator = state.panel.querySelector('.ctrlf-status-indicator');
const statusText = state.panel.querySelector('.ctrlf-status-text');
indicator.classList.toggle('active', isEnabled);
statusText.textContent = `Protection ${isEnabled ? 'ON' : 'OFF'} (${source === 'site' ? 'site override' : 'global'})`;
// Toggle states
state.panel.querySelector('#globalToggle').checked = state.global;
state.panel.querySelector('#siteToggle').checked = state.site !== null ? state.site : state.global;
state.panel.querySelector('#showUIToggle').checked = state.showUI;
// Reset button state
const resetBtn = state.panel.querySelector('#resetSite');
resetBtn.disabled = state.site === null;
resetBtn.style.opacity = state.site === null ? '0.5' : '1';
// Site toggle visual state
const siteLabel = state.panel.querySelector('#siteToggle').closest('.ctrlf-toggle-label');
const siteText = siteLabel.querySelector('.ctrlf-toggle-text');
if (state.site !== null) {
siteLabel.style.opacity = '1';
siteText.textContent = `Override for ${location.hostname} (${state.site ? 'ON' : 'OFF'})`;
} else {
siteLabel.style.opacity = '0.7';
siteText.textContent = `Override for ${location.hostname} (using global)`;
}
},
setupEventListeners() {
const panel = state.panel;
// Close button
panel.querySelector('.ctrlf-close').addEventListener('click', () => this.hide());
// Global toggle
panel.querySelector('#globalToggle').addEventListener('change', (e) => {
state.global = e.target.checked;
GM_setValue(CONFIG.keys.GLOBAL_PROTECTION, state.global);
this.update();
});
// Site toggle
panel.querySelector('#siteToggle').addEventListener('change', (e) => {
if (state.site === null) {
state.site = !state.global;
} else {
state.site = e.target.checked;
}
GM_setValue(CONFIG.keys.SITE_PROTECTION, state.site);
this.update();
});
// Reset button
panel.querySelector('#resetSite').addEventListener('click', () => {
protection.resetSite();
});
// Show UI toggle
panel.querySelector('#showUIToggle').addEventListener('change', (e) => {
state.showUI = e.target.checked;
GM_setValue(CONFIG.keys.SHOW_UI, state.showUI);
});
// Shortcut buttons
panel.querySelectorAll('.ctrlf-shortcut-btn').forEach(btn => {
btn.addEventListener('click', () => keyboard.recordShortcut(btn));
});
}
};
// UTILITY FUNCTIONS
const utils = {
createElement(tag, className, html) {
const el = document.createElement(tag);
el.className = className;
el.innerHTML = html;
return el;
},
makeDraggable(element, handle) {
let isDragging = false;
let startX, startY, startLeft, startTop;
const controller = new AbortController();
const options = { signal: controller.signal };
const handleMouseDown = (e) => {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
const rect = element.getBoundingClientRect();
startLeft = rect.left;
startTop = rect.top;
e.preventDefault();
};
const handleMouseMove = (e) => {
if (!isDragging) return;
const newLeft = Math.max(0, Math.min(window.innerWidth - element.offsetWidth, startLeft + e.clientX - startX));
const newTop = Math.max(0, Math.min(window.innerHeight - element.offsetHeight, startTop + e.clientY - startY));
element.style.left = newLeft + 'px';
element.style.top = newTop + 'px';
element.style.right = 'auto';
};
const handleMouseUp = () => {
isDragging = false;
};
handle.addEventListener('mousedown', handleMouseDown, options);
document.addEventListener('mousemove', handleMouseMove, options);
document.addEventListener('mouseup', handleMouseUp, options);
// Return cleanup function
return () => controller.abort();
},
showNotification(message) {
const notification = this.createElement('div', 'ctrlf-notification', message);
document.body.appendChild(notification);
const animation = notification.animate([
{ opacity: 0, transform: 'translateX(-50%) translateY(-10px)' },
{ opacity: 1, transform: 'translateX(-50%) translateY(0)' }
], { duration: 300, easing: 'ease-out' });
setTimeout(() => {
const fadeOut = notification.animate([
{ opacity: 1 },
{ opacity: 0 }
], { duration: 300, easing: 'ease-in' });
fadeOut.onfinish = () => notification.remove();
}, CONFIG.ui.notificationDuration);
},
addStyles() {
if (document.querySelector('#ctrlf-styles')) return;
const style = document.createElement('style');
style.id = 'ctrlf-styles';
style.textContent = `
.ctrlf-panel {
position: fixed; top: 20px; right: 20px; width: 340px;
background: rgba(255,255,255,0.95); color: #333; border-radius: 12px;
font: 14px -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
z-index: 999999; box-shadow: 0 10px 40px rgba(0,0,0,0.15);
backdrop-filter: blur(20px); border: 1px solid rgba(0,0,0,0.1);
cursor: move; user-select: none;
}
@media (prefers-color-scheme: dark) {
.ctrlf-panel {
background: rgba(30,30,30,0.95); color: #fff;
border: 1px solid rgba(255,255,255,0.1);
}
}
.ctrlf-header {
display: flex; justify-content: space-between; align-items: center;
padding: 16px 20px; border-bottom: 1px solid rgba(0,0,0,0.1);
}
@media (prefers-color-scheme: dark) {
.ctrlf-header { border-bottom: 1px solid rgba(255,255,255,0.1); }
}
.ctrlf-title { font-weight: 600; font-size: 16px; }
.ctrlf-close {
background: none; border: none; color: currentColor; font-size: 24px;
cursor: pointer; padding: 4px; width: 32px; height: 32px;
display: flex; align-items: center; justify-content: center;
border-radius: 6px; transition: background-color 0.2s;
}
.ctrlf-close:hover { background: rgba(0,0,0,0.1); }
@media (prefers-color-scheme: dark) {
.ctrlf-close:hover { background: rgba(255,255,255,0.1); }
}
.ctrlf-content { padding: 20px; }
.ctrlf-section { margin-bottom: 24px; }
.ctrlf-section:last-child { margin-bottom: 0; }
.ctrlf-section h4 { margin: 0 0 12px 0; font-size: 14px; font-weight: 600; color: #666; }
@media (prefers-color-scheme: dark) {
.ctrlf-section h4 { color: #999; }
}
.ctrlf-status {
display: flex; align-items: center; gap: 12px; padding: 16px;
background: rgba(0,0,0,0.05); border-radius: 8px; margin-bottom: 24px;
}
@media (prefers-color-scheme: dark) {
.ctrlf-status { background: rgba(255,255,255,0.05); }
}
.ctrlf-status-indicator {
width: 12px; height: 12px; border-radius: 50%; background: #ef4444;
transition: background-color 0.3s;
}
.ctrlf-status-indicator.active { background: #22c55e; }
.ctrlf-status-text { font-weight: 500; font-size: 15px; }
.ctrlf-control-group { margin-bottom: 16px; }
.ctrlf-control-group:last-child { margin-bottom: 0; }
.ctrlf-toggle-label {
display: flex; align-items: center; gap: 12px; cursor: pointer; margin-bottom: 4px;
}
.ctrlf-toggle { display: none; }
.ctrlf-toggle-slider {
width: 44px; height: 24px; background: #ddd; border-radius: 12px;
position: relative; transition: background-color 0.3s; flex-shrink: 0;
}
.ctrlf-toggle-slider::before {
content: ''; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px;
background: white; border-radius: 50%; transition: transform 0.3s;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
}
.ctrlf-toggle:checked + .ctrlf-toggle-slider { background: #007acc; }
.ctrlf-toggle:checked + .ctrlf-toggle-slider::before { transform: translateX(20px); }
.ctrlf-toggle-text { font-weight: 500; }
.ctrlf-control-desc {
font-size: 12px; color: #666; margin-left: 56px;
}
@media (prefers-color-scheme: dark) {
.ctrlf-control-desc { color: #999; }
}
.ctrlf-reset-btn {
background: none; border: 1px solid #ddd; color: #666; padding: 8px 16px;
border-radius: 6px; cursor: pointer; font-size: 13px; transition: all 0.2s;
margin-left: 56px;
}
.ctrlf-reset-btn:hover { background: rgba(0,0,0,0.05); border-color: #999; }
@media (prefers-color-scheme: dark) {
.ctrlf-reset-btn { border-color: #555; color: #999; }
.ctrlf-reset-btn:hover { background: rgba(255,255,255,0.05); border-color: #777; }
}
.ctrlf-shortcut-item {
display: flex; align-items: center; margin-bottom: 12px;
}
.ctrlf-shortcut-item label { width: 100px; font-size: 13px; color: #666; }
@media (prefers-color-scheme: dark) {
.ctrlf-shortcut-item label { color: #999; }
}
.ctrlf-shortcut-btn {
flex: 1; padding: 8px 12px; background: rgba(0,0,0,0.05);
border: 1px solid #ddd; border-radius: 6px; color: currentColor;
font: 12px 'SF Mono',Monaco,monospace; cursor: pointer;
transition: all 0.2s; text-align: left;
}
.ctrlf-shortcut-btn:hover { background: rgba(0,0,0,0.1); border-color: #999; }
.ctrlf-shortcut-btn:focus, .ctrlf-shortcut-btn.recording {
outline: none; border-color: #007acc; box-shadow: 0 0 0 3px rgba(0,122,204,0.1);
background: rgba(0,122,204,0.05);
}
.ctrlf-shortcut-display { font-weight: 500; }
.ctrlf-shortcut-hint { font-size: 10px; opacity: 0.6; margin-left: 8px; }
.ctrlf-shortcut-btn.recording .ctrlf-shortcut-hint { opacity: 1; color: #007acc; }
@media (prefers-color-scheme: dark) {
.ctrlf-shortcut-btn { background: rgba(255,255,255,0.05); border-color: #555; }
.ctrlf-shortcut-btn:hover { background: rgba(255,255,255,0.1); border-color: #777; }
}
`;
document.head.appendChild(style);
}
};
// INITIALIZATION
function init() {
window.addEventListener('keydown', (e) => keyboard.handleKeydown(e), true);
// Show UI after delay if enabled
if (state.showUI) {
setTimeout(() => ui.show(), CONFIG.ui.showDelay);
}
console.log(`Force Ctrl+F loaded - Protection: ${protection.isEnabled() ? 'ON' : 'OFF'} (${protection.getSource()}), UI: ${state.showUI ? 'ON' : 'OFF'}`);
}
init();
})();