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

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==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);
})();