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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

ستحتاج إلى تثبيت إضافة مثل 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);
})();