Google Escape Hatch

Botão para voltar rapidamente à última pesquisa do Google (específico por aba).

// ==UserScript==
// @name         Google Escape Hatch
// @namespace    https://github.com/BrunoFortunato/google-escape-hatch
// @version      0.6
// @description  Botão para voltar rapidamente à última pesquisa do Google (específico por aba).
// @author       Bruno Fortunato (modificado para funcionar por aba)
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @grant        GM_getTab
// @grant        GM_saveTab
// @grant        GM_deleteTab
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // Função para gerar uma chave única para cada aba
    async function getTabSpecificKey(baseKey) {
        const tab = await GM_getTab();
        if (tab && !tab.tabId) { // Adicionada verificação se 'tab' existe
            tab.tabId = Date.now(); // Fallback se tabId não estiver disponível imediatamente
            GM_saveTab(tab);
        }
        return `${baseKey}_${tab?.tabId ? tab.tabId : 'fallback'}`; // Usando optional chaining e fallback
    }

    // Detecta se é uma página de pesquisa do Google
    const isGoogleSearchPage = (url = window.location.href) => {
        try {
            const u = new URL(url);
            return u.hostname.includes('google.') && (u.pathname === '/search' || u.searchParams.has('q'));
        } catch {
            return false;
        }
    };

    async function main() {
        const tabSpecificLastGoogleSearchKey = await getTabSpecificKey('lastGoogleSearch');
        const tabSpecificLastScrollPositionKey = await getTabSpecificKey('lastScrollPosition');
        const tabSpecificCameFromGoogleKey = await getTabSpecificKey('cameFromGoogle');

        let lastGoogleSearch = await GM_getValue(tabSpecificLastGoogleSearchKey, null);
        let lastScrollPosition = await GM_getValue(tabSpecificLastScrollPositionKey, 0);
        let cameFromGoogle = await GM_getValue(tabSpecificCameFromGoogleKey, false);

        // Previne falso positivo se for reload direto
        if (performance?.navigation?.type === 1) {
            GM_setValue(tabSpecificCameFromGoogleKey, false);
        }

        // Salva dados ao clicar num link vindo da pesquisa
        document.addEventListener('click', async (e) => {
            const link = e.target.closest('a');
            if (link && isGoogleSearchPage(window.location.href)) {
                await GM_setValue(tabSpecificLastGoogleSearchKey, window.location.href);
                await GM_setValue(tabSpecificLastScrollPositionKey, window.scrollY);
                await GM_setValue(tabSpecificCameFromGoogleKey, true);
            }
        });

        // Restaura rolagem na página de pesquisa
        window.addEventListener("load", async () => {
            if (isGoogleSearchPage() && await GM_getValue(tabSpecificLastGoogleSearchKey) === location.href) {
                setTimeout(async () => {
                    window.location.href = await GM_getValue(tabSpecificLastGoogleSearchKey);
                }, 1000);
            }
        });

        // Cria botão de escape
        async function createEscapeButton() {
            const currentLastGoogleSearch = await GM_getValue(tabSpecificLastGoogleSearchKey);
            const currentCameFromGoogle = await GM_getValue(tabSpecificCameFromGoogleKey);

            if (!currentLastGoogleSearch || isGoogleSearchPage() || !currentCameFromGoogle) return;
            if (document.getElementById("googleEscapeButton")) return;

            const btn = document.createElement('button');
            btn.id = 'googleEscapeButton';
            btn.className = 'google-escape';
            btn.setAttribute('aria-label', 'Voltar para o Google');
            btn.setAttribute('role', 'button');
            btn.innerHTML = '←';

            btn.addEventListener('click', async () => {
                if (btn.classList.contains('expanded')) {
                    btn.classList.remove('expanded');
                    btn.innerHTML = '←';
                    await GM_setValue(tabSpecificCameFromGoogleKey, false);
                    window.location.href = await GM_getValue(tabSpecificLastGoogleSearchKey);
                } else {
                    btn.classList.add('expanded');
                    btn.innerHTML = '← Voltar para o Google';
                }
            });

            document.addEventListener('click', (event) => {
                if (btn.classList.contains('expanded') && !btn.contains(event.target)) {
                    btn.classList.remove('expanded');
                    btn.innerHTML = '←';
                }
            });

            document.body.appendChild(btn);
        }

        // Observa mudanças no <title> (SPA)
        const titleObserver = new MutationObserver(createEscapeButton);
        const titleElement = document.querySelector('head > title');
        if (titleElement) {
            titleObserver.observe(titleElement, { childList: true });
        }

        // Observa mudanças no body (para sites dinâmicos)
        const bodyObserver = new MutationObserver(createEscapeButton);
        if (document.body) {
            bodyObserver.observe(document.body, { childList: true, subtree: true });
        }

        // Reinsere botão se for removido
        const survivalObserver = new MutationObserver(() => {
            if (!document.getElementById("googleEscapeButton")) {
                createEscapeButton();
            }
        });
        survivalObserver.observe(document.body, { childList: true, subtree: true });

        // Garante que o botão fique no topo da hierarquia visual
        function ensureButtonTopLevel() {
            const btn = document.getElementById("googleEscapeButton");
            if (btn && btn.parentNode !== document.body) {
                document.body.appendChild(btn);
            }
        }
        setInterval(ensureButtonTopLevel, 2000);

        // Tentativa recorrente de criação (caso tudo falhe)
        let fallbackInterval = setInterval(() => {
            if (!document.getElementById("googleEscapeButton")) {
                createEscapeButton();
            } else {
                clearInterval(fallbackInterval);
            }
        }, 2000);

        // Corrige posição ao rolar
        window.addEventListener("scroll", () => {
            const button = document.getElementById("googleEscapeButton");
            if (button) {
                button.style.bottom = `20px`;
            }
        });

        // Estilo do botão (mantido igual)
        GM_addStyle(`
            #googleEscapeButton {
                position: fixed !important;
                left: 20px !important;
                bottom: 20px !important;
                z-index: 2147483647 !important;
                background-color: #2196F3 !important;
                color: white !important;
                border: none !important;
                padding: 10px !important;
                border-radius: 50% !important;
                cursor: pointer !important;
                font-size: 20px !important;
                transition: all 0.3s ease-in-out !important;
                width: 40px !important;
                height: 40px !important;
                text-align: center !important;
                display: flex !important;
                align-items: center !important;
                justify-content: center !important;
                overflow: visible !important;
                white-space: nowrap !important;
                opacity: 1 !important;
                visibility: visible !important;
            }
            #googleEscapeButton.expanded {
                width: auto !important;
                min-width: 180px !important;
                height: 50px !important;
                border-radius: 10px !important;
                padding: 10px 15px !important;
            }
            #googleEscapeButton:hover {
                background-color: #1976D2 !important;
            }
            @media (prefers-color-scheme: dark) {
                #googleEscapeButton {
                    background-color: #90caf9 !important;
                    color: #000 !important;
                }
            }
        `);
    }

    main();
})();