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