Trigger full page load for Jira Cloud and Confluence Cloud

Trigger the page to load parts that are normally (since August 2025) lazily loaded, when that part of the page this shown. Works on Jira Cloud and Confluence Cloud as of August 2025.

As of 2025-08-19. See the latest version.

// ==UserScript==
// @name        Trigger full page load for Jira Cloud and Confluence Cloud
// @namespace   https://greasyfork.org/users/1047370
// @description Trigger the page to load parts that are normally (since August 2025) lazily loaded, when that part of the page this shown.  Works on Jira Cloud and Confluence Cloud as of August 2025.
// @include     https://*.atlassian.net/*
// @include     https://*.jira.com/*
// @match       https://*.atlassian.net/*
// @match       https://*.jira.com/*
// @version     0.1
// @author      Marnix Klooster <[email protected]>
// @copyright   public domain
// @license     public domain
// @homepage    https://greasyfork.org/scripts/546394
// @grant       none
// @run-at      document-start
// ==/UserScript==

(function() {
    'use strict';
    
    const processedElements = new WeakSet();
    let isProcessing = false;
    
    const waitForPageLoad = () => {
        return new Promise(resolve => {
            if (document.readyState === 'complete') {
                resolve();
            } else {
                window.addEventListener('load', resolve);
            }
        });
    };
    
    const createGlassPane = () => {
        const pane = document.createElement('div');
        pane.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            background: transparent;
            z-index: 9999;
            pointer-events: none;
            display: flex;
            align-items: center;
            justify-content: center;
        `;
        
        const progressContainer = document.createElement('div');
        progressContainer.style.cssText = `
            background: white;
            border-radius: 8px;
            padding: 24px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
            min-width: 300px;
            text-align: center;
            opacity: 0;
            transition: opacity 0.3s ease;
        `;
        
        const progressText = document.createElement('div');
        progressText.textContent = 'Loading content...';
        progressText.style.cssText = `
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
            font-size: 14px;
            color: #172B4D;
            margin-bottom: 16px;
            font-weight: 500;
        `;
        
        const progressTrack = document.createElement('div');
        progressTrack.style.cssText = `
            width: 100%;
            height: 4px;
            background: #DFE1E6;
            border-radius: 2px;
            overflow: hidden;
        `;
        
        const progressBar = document.createElement('div');
        progressBar.style.cssText = `
            width: 0%;
            height: 100%;
            background: #0052CC;
            border-radius: 2px;
            transition: width 0.3s ease;
        `;
        
        progressTrack.appendChild(progressBar);
        progressContainer.appendChild(progressText);
        progressContainer.appendChild(progressTrack);
        pane.appendChild(progressContainer);
        document.body.appendChild(pane);
        
        const showProgress = () => {
            pane.style.background = 'rgba(128, 128, 128, 0.5)';
            progressContainer.style.opacity = '1';
        };
        
        return { pane, progressBar, showProgress };
    };
    
    const findEmptyDivs = () => {
        return Array.from(document.querySelectorAll('div'))
            .filter(div => 
                div.textContent.trim() === '' && 
                div.attributes.length === 0 &&
                !processedElements.has(div)
            );
    };
    
    const waitForStability = (element, timeout = 50) => {
        return new Promise(resolve => {
            let timer;
            const observer = new MutationObserver(() => {
                clearTimeout(timer);
                timer = setTimeout(() => {
                    observer.disconnect();
                    resolve();
                }, timeout);
            });
            
            observer.observe(element, {
                childList: true,
                subtree: true,
                attributes: true,
                characterData: true
            });
            
            // Start initial timer in case no mutations occur
            timer = setTimeout(() => {
                observer.disconnect();
                resolve();
            }, timeout);
        });
    };
    
    const scrollToElement = async (element) => {
        element.scrollIntoView({ behavior: 'smooth', block: 'center' });
        await waitForStability(element);
        processedElements.add(element);
    };
    
    const processLazyElements = async () => {
        if (isProcessing) return;
        isProcessing = true;
        
        const originalScrollY = window.scrollY;
        const emptyDivs = findEmptyDivs();
        
        if (emptyDivs.length === 0) {
            isProcessing = false;
            return;
        }
        
        console.log(`Processing ${emptyDivs.length} lazy-loaded elements`);
        const { pane: glassPane, progressBar, showProgress } = createGlassPane();
        
        // Wait 200ms before showing progress to prevent flashing for quick operations
        const showTimer = setTimeout(showProgress, 200);
        
        try {
            for (let i = 0; i < emptyDivs.length; i++) {
                const progress = ((i + 1) / emptyDivs.length) * 100;
                progressBar.style.width = `${progress}%`;
                
                await scrollToElement(emptyDivs[i]);
            }
            
            // Scroll back to original position
            window.scrollTo({ top: originalScrollY, behavior: 'smooth' });
        } finally {
            clearTimeout(showTimer);
            glassPane.remove();
            isProcessing = false;
        }
    };
    
    const observePageChanges = () => {
        const observer = new MutationObserver(() => {
            // Debounce to avoid excessive processing
            clearTimeout(observer.timer);
            observer.timer = setTimeout(processLazyElements, 100);
        });
        
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    };
    
    const init = async () => {
        await waitForPageLoad();
        console.log('Page fully loaded, starting lazy element processing');
        
        await processLazyElements();
        observePageChanges();
    };
    
    init();
    
})();