Screenshot with dragging selection

High-precision selection screenshot for Via Browser (CORS Image & Gradient Fix), works on Via browser, xbrowser perfectly.Almost all websites are supported

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Screenshot with dragging selection 
// @namespace    http://tampermonkey.net/
// @version      20.05.2026
// @description  High-precision selection screenshot for Via Browser (CORS Image & Gradient Fix), works on Via browser, xbrowser perfectly.Almost all websites are supported 
// @author       Spuramgemi
// @match        *://*/*
// @run-at       document-start
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @require      https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js
// ==/UserScript==

(function() {
    'use strict';

    let btn, isSelecting = false, startX, startY, selectionBox;
    let menuCommandId = null;

    // Toggle function that can be called from both button and GM menu
    function toggleSelectionMode() {
        if (!btn) return;
        isSelecting = !isSelecting;
        btn.style.opacity = isSelecting ? '0.7' : '0.35';
        btn.innerHTML = isSelecting ? '❌' : '✂️';
    }

    function attachSelectionLogic() {
        window.removeEventListener('touchstart', handleStart, { passive: false });
        window.removeEventListener('touchmove', handleMove, { passive: false });
        window.removeEventListener('touchend', handleEnd);

        window.addEventListener('touchstart', handleStart, { passive: false });
        window.addEventListener('touchmove', handleMove, { passive: false });
        window.addEventListener('touchend', handleEnd);
    }

    function handleStart(e) {
        if (!isSelecting || e.target === btn) return;
        const touch = e.touches[0];
        startX = touch.clientX;
        startY = touch.clientY;

        selectionBox = document.createElement('div');
        selectionBox.style.cssText = `position:fixed; border:2px solid #007AFF; background:rgba(0,122,255,0.2); z-index:99998; left:${startX}px; top:${startY}px; pointer-events:none;`;
        document.body.appendChild(selectionBox);
    }

    function handleMove(e) {
        if (!isSelecting || !selectionBox) return;
        e.preventDefault();
        const touch = e.touches[0];
        const curX = touch.clientX;
        const curY = touch.clientY;

        const width = Math.abs(curX - startX);
        const height = Math.abs(curY - startY);
        const left = Math.min(curX, startX);
        const top = Math.min(curY, startY);

        selectionBox.style.width = width + 'px';
        selectionBox.style.height = height + 'px';
        selectionBox.style.left = left + 'px';
        selectionBox.style.top = top + 'px';
    }

    function handleEnd() {
        if (!isSelecting || !selectionBox) return;

        const rect = selectionBox.getBoundingClientRect();
        selectionBox.remove();
        selectionBox = null;
        isSelecting = false;
        btn.style.background = 'transparent';
        btn.style.opacity = '0.35';
        btn.innerHTML = '✂️';

        setTimeout(() => {
            if (!window.html2canvas) {
                alert("html2canvas not loaded yet, please retry.");
                return;
            }

            const bodyRect = document.body.getBoundingClientRect();
            const docRect = document.documentElement.getBoundingClientRect();
            
            const scrollX = window.scrollX || window.pageXOffset || Math.abs(docRect.left) || Math.abs(bodyRect.left) || 0;
            const scrollY = window.scrollY || window.pageYOffset || Math.abs(docRect.top) || Math.abs(bodyRect.top) || 0;

            const dpr = window.devicePixelRatio || 1;
            
            const targetWidth = Math.max(document.documentElement.clientWidth, window.innerWidth, document.body.clientWidth);
            const targetHeight = Math.max(document.body.scrollHeight, document.documentElement.scrollHeight);

            window.html2canvas(document.body, {
                x: Math.floor(rect.left + scrollX),
                y: Math.floor(rect.top + scrollY),
                width: Math.ceil(rect.width), 
                height: Math.ceil(rect.height),
                scrollX: 0,
                scrollY: 0,
                windowWidth: targetWidth,
                windowHeight: targetHeight,
                scale: dpr,
                useCORS: true,       
                allowTaint: false, // Turned false so .toDataURL() never breaks
                logging: false,
                onclone: async (clonedDoc) => {
                    // 1. CRASH PROTECTION FILTER: Remove dynamic gradients
                    const allClonedElements = clonedDoc.getElementsByTagName('*');
                    for (let i = 0; i < allClonedElements.length; i++) {
                        const style = window.getComputedStyle(allClonedElements[i]);
                        if (style.backgroundImage && style.backgroundImage.includes('gradient')) {
                            allClonedElements[i].style.setProperty('background-image', 'none', 'important');
                        }
                    }

                    // 2. CORS FORCE FILTER: Convert images inside selection to base64 inline strings
                    const clonedImages = clonedDoc.getElementsByTagName('img');
                    const promises = Array.from(clonedImages).map(img => {
                        return new Promise((resolve) => {
                            if (!img.src || img.src.startsWith('data:')) return resolve();
                            
                            // Native fetch bypass for same-origin or loose CORS endpoints
                            fetch(img.src)
                                .then(response => response.blob())
                                .then(blob => {
                                    const reader = new FileReader();
                                    reader.onloadend = () => {
                                        img.src = reader.result;
                                        resolve();
                                    };
                                    reader.readAsDataURL(blob);
                                })
                                .catch(() => {
                                    // Fallback: Force crossOrigin attribute as backup
                                    img.crossOrigin = "anonymous";
                                    resolve();
                                });
                        });
                    });
                    await Promise.all(promises);
                }
            }).then(canvas => {
                const overlay = document.createElement('div');
                overlay.style.cssText = 'position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.85); z-index:100000; display:flex; flex-direction:column; align-items:center; justify-content:center;';

                const preview = document.createElement('div');
                preview.style.cssText = 'background:white; padding:15px; border-radius:8px; max-width:90%; max-height:75%; overflow:auto; text-align:center;';
                
                const notice = document.createElement('p');
                notice.innerHTML = "📸 <b>Preview Ready</b><br><span style='font-size:12px;color:#666;'>Long press the preview image to save it natively.</span>";
                notice.style.cssText = 'margin:0 0 12px 0; font-family:sans-serif; font-size:14px; color:#333; line-height:1.4;';
                preview.appendChild(notice);

                const visualWrapper = document.createElement('div');
                visualWrapper.style.cssText = 'position:relative; display:inline-block;';

                canvas.style.cssText = `
                    max-width: 100% !important;
                    height: auto !important;
                    display: block !important;
                    margin: 0 auto !important;
                    padding-right: 4px !important;
                    box-sizing: border-box !important;
                `;
                
                visualWrapper.appendChild(canvas);

                try {
                    const nativeImgPlaceholder = document.createElement('img');
                    nativeImgPlaceholder.src = canvas.toDataURL("image/png");
                    nativeImgPlaceholder.style.cssText = 'position:absolute; top:0; left:0; width:100%; height:100%; opacity:0.01; -webkit-touch-callout:default !important;';
                    visualWrapper.appendChild(nativeImgPlaceholder);
                } catch(e) {
                    canvas.style.cssText += '; -webkit-touch-callout:default !important; user-select:element !important;';
                }

                preview.appendChild(visualWrapper);

                const btnRow = document.createElement('div');
                btnRow.style.cssText = 'margin-top:14px; display:flex; gap:10px; justify-content:center;';

                const confirmBtn = document.createElement('button');
                confirmBtn.textContent = 'Save File';
                confirmBtn.style.cssText = 'padding:10px 20px; background:#007AFF; color:white; border:none; border-radius:6px; font-size:15px; font-weight:bold;';
                confirmBtn.onclick = () => {
                    try {
                        const link = document.createElement('a');
                        link.download = `crop_${Date.now()}.png`;
                        link.href = canvas.toDataURL("image/png");
                        link.click();
                        overlay.remove();
                    } catch(e) {
                        alert("Please use Via Browser's native long-press on the image below to save directly.");
                    }
                };

                const cancelBtn = document.createElement('button');
                cancelBtn.textContent = 'Cancel';
                cancelBtn.style.cssText = 'padding:10px 20px; background:#FF3B30; color:white; border:none; border-radius:6px; font-size:15px; font-weight:bold;';
                cancelBtn.onclick = () => overlay.remove();

                btnRow.appendChild(confirmBtn);
                btnRow.appendChild(cancelBtn);
                preview.appendChild(btnRow);
                overlay.appendChild(preview);
                document.body.appendChild(overlay);
            }).catch(err => alert("Capture failed: " + err));
        }, 100);
    }

    function createButton() {
        if (btn && (document.body.contains(btn) || document.documentElement.contains(btn))) return;

        btn = document.createElement('div');
        btn.innerHTML = '✂️';
        btn.style.cssText = `
            position:fixed !important;
            bottom:140px !important;
            right:12px !important;
            z-index:2147483647 !important;
            width:28px !important;
            height:28px !important;
            background:transparent !important;
            color:rgba(0,0,0,0.35) !important;
            border:none !important;
            border-radius:50% !important;
            display:flex !important;
            align-items:center !important;
            justify-content:center !important;
            font-size:14px !important;
            box-shadow:none !important;
            padding:0 !important;
            margin:0 !important;
            touch-action:none !important;
            opacity:0.35 !important;
        `;
        if (document.body) document.body.appendChild(btn);
        document.documentElement.appendChild(btn);

        btn.onclick = (e) => {
            e.preventDefault();
            toggleSelectionMode();
        };

        attachSelectionLogic();
    }

    // Register GM menu command
    function registerMenuCommand() {
        if (menuCommandId !== null) {
            GM_unregisterMenuCommand(menuCommandId);
        }
        menuCommandId = GM_registerMenuCommand("📸 Toggle Screenshot Mode", function() {
            if (btn) {
                toggleSelectionMode();
            }
        });
    }

    function ensureButton() {
        if (!document.body) {
            setTimeout(ensureButton, 100);
            return;
        }
        createButton();
        registerMenuCommand();
    }

    if (document.readyState === "loading") {
        document.addEventListener("DOMContentLoaded", ensureButton);
    } else {
        ensureButton();
    }

    const observer = new MutationObserver(() => {
        if (!btn || (!document.body.contains(btn) && !document.documentElement.contains(btn))) {
            ensureButton();
        }
        attachSelectionLogic();
        registerMenuCommand();
    });
    observer.observe(document.documentElement, { childList: true, subtree: true });

    document.addEventListener("readystatechange", () => {
        if (document.readyState === "interactive" || document.readyState === "complete") {
            ensureButton();
        }
    });

    setInterval(ensureButton, 1500);
})();