Back to blog
PerformanceJanuary 1, 2024· 19 min read

Mobile Performance Testing and Optimization: A Developer's Guide

A practical Fusebox guide to mobile performance testing and optimization.

Mobile Performance Testing and Optimization: A Developer's Guide

Mobile users are unforgiving—53% abandon sites that take over 3 seconds to load. Yet most developers test on high-end devices with fast WiFi, missing the reality of budget phones on spotty 3G. This guide provides JavaScript tools to test real mobile performance, simulate network conditions, and optimize for the constraints that actually matter.

Why Mobile Performance Is Different

In 2023, an e-commerce giant discovered their mobile conversion rate was 70% lower than desktop. The culprit? Their site consumed 6MB of JavaScript and required 45 seconds of CPU time on budget Android phones. After optimization, they reduced mobile load time by 80% and doubled mobile revenue. Mobile isn't just a smaller screen—it's an entirely different performance profile.

Mobile Performance Analyzer

Comprehensive mobile performance testing in the browser:

// Mobile Performance Analyzer
class MobilePerformanceAnalyzer {
    constructor() {
        this.metrics = {
            device: {},
            network: {},
            resources: [],
            interactions: [],
            memory: {}
        };
    }
    
    // Run comprehensive mobile analysis
    async analyzeMobilePerformance() {
        const analysis = {
            device: this.detectDevice(),
            viewport: this.analyzeViewport(),
            network: await this.estimateNetworkSpeed(),
            cpu: await this.measureCPUPerformance(),
            memory: this.analyzeMemoryUsage(),
            touch: this.analyzeTouchPerformance(),
            resources: await this.analyzeResourceLoading(),
            rendering: this.analyzeRenderingPerformance(),
            battery: await this.analyzeBatteryImpact(),
            recommendations: []
        };
        
        analysis.recommendations = this.generateRecommendations(analysis);
        return analysis;
    }
    
    // Detect device capabilities
    detectDevice() {
        const ua = navigator.userAgent;
        const device = {
            type: this.getDeviceType(),
            os: this.getOS(),
            browser: this.getBrowser(),
            // Hardware capabilities
            cores: navigator.hardwareConcurrency || 1,
            memory: navigator.deviceMemory || 'unknown',
            connection: navigator.connection || {},
            // Feature support
            touchPoints: navigator.maxTouchPoints || 0,
            hasTouch: 'ontouchstart' in window,
            hasPointer: 'PointerEvent' in window,
            // Performance hints
            reducedMotion: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
            saveData: navigator.connection?.saveData || false,
            // Screen
            pixelRatio: window.devicePixelRatio || 1,
            screen: {
                width: screen.width,
                height: screen.height,
                availWidth: screen.availWidth,
                availHeight: screen.availHeight
            }
        };
        
        // Estimate device tier
        device.tier = this.estimateDeviceTier(device);
        
        return device;
    }
    
    // Get device type
    getDeviceType() {
        const ua = navigator.userAgent;
        if (/tablet|ipad/i.test(ua)) return 'tablet';
        if (/mobile|android|iphone/i.test(ua)) return 'mobile';
        return 'desktop';
    }
    
    // Get operating system
    getOS() {
        const ua = navigator.userAgent;
        if (/android/i.test(ua)) return 'Android';
        if (/iphone|ipad|ipod/i.test(ua)) return 'iOS';
        if (/windows phone/i.test(ua)) return 'Windows Phone';
        return 'Unknown';
    }
    
    // Get browser
    getBrowser() {
        const ua = navigator.userAgent;
        if (/chrome/i.test(ua) && !/edge/i.test(ua)) return 'Chrome';
        if (/safari/i.test(ua) && !/chrome/i.test(ua)) return 'Safari';
        if (/firefox/i.test(ua)) return 'Firefox';
        if (/edge/i.test(ua)) return 'Edge';
        return 'Unknown';
    }
    
    // Estimate device tier (low, mid, high)
    estimateDeviceTier(device) {
        let score = 0;
        
        // CPU cores
        if (device.cores >= 8) score += 3;
        else if (device.cores >= 4) score += 2;
        else score += 1;
        
        // RAM
        if (device.memory >= 8) score += 3;
        else if (device.memory >= 4) score += 2;
        else if (device.memory >= 2) score += 1;
        
        // Connection
        if (device.connection.effectiveType === '4g') score += 2;
        else if (device.connection.effectiveType === '3g') score += 1;
        
        // Pixel ratio (retina displays)
        if (device.pixelRatio >= 3) score += 1;
        
        // Determine tier
        if (score >= 8) return 'high';
        if (score >= 5) return 'mid';
        return 'low';
    }
    
    // Analyze viewport and responsive behavior
    analyzeViewport() {
        const viewport = {
            width: window.innerWidth,
            height: window.innerHeight,
            orientation: screen.orientation?.type || 'unknown',
            isPortrait: window.innerHeight > window.innerWidth,
            // Visual viewport (accounts for keyboard, etc)
            visualViewport: {
                width: window.visualViewport?.width || window.innerWidth,
                height: window.visualViewport?.height || window.innerHeight,
                scale: window.visualViewport?.scale || 1
            },
            // Check for viewport meta tag
            hasViewportMeta: !!document.querySelector('meta[name="viewport"]'),
            viewportContent: document.querySelector('meta[name="viewport"]')?.content
        };
        
        // Test responsive breakpoints
        viewport.breakpoints = this.testBreakpoints();
        
        return viewport;
    }
    
    // Test responsive breakpoints
    testBreakpoints() {
        const breakpoints = {
            xs: 320,
            sm: 640,
            md: 768,
            lg: 1024,
            xl: 1280
        };
        
        const current = window.innerWidth;
        let activeBreakpoint = 'xs';
        
        for (const [name, width] of Object.entries(breakpoints)) {
            if (current >= width) activeBreakpoint = name;
        }
        
        return {
            current: activeBreakpoint,
            width: current,
            breakpoints
        };
    }
    
    // Estimate network speed
    async estimateNetworkSpeed() {
        const connection = navigator.connection || {};
        
        const network = {
            effectiveType: connection.effectiveType || 'unknown',
            downlink: connection.downlink || 'unknown',
            rtt: connection.rtt || 'unknown',
            saveData: connection.saveData || false,
            type: connection.type || 'unknown'
        };
        
        // Measure actual download speed
        try {
            const testUrl = '/favicon.ico?' + Date.now();
            const startTime = performance.now();
            
            const response = await fetch(testUrl);
            const blob = await response.blob();
            
            const endTime = performance.now();
            const duration = endTime - startTime;
            const sizeInBits = blob.size * 8;
            
            network.measuredSpeed = {
                bps: sizeInBits / (duration / 1000),
                mbps: (sizeInBits / (duration / 1000)) / 1000000,
                latency: duration,
                testSize: blob.size
            };
        } catch (e) {
            network.measuredSpeed = null;
        }
        
        // Classify network quality
        network.quality = this.classifyNetworkQuality(network);
        
        return network;
    }
    
    // Classify network quality
    classifyNetworkQuality(network) {
        if (network.saveData) return 'save-data';
        
        const effectiveType = network.effectiveType;
        if (effectiveType === 'slow-2g') return 'very-poor';
        if (effectiveType === '2g') return 'poor';
        if (effectiveType === '3g') return 'moderate';
        if (effectiveType === '4g') return 'good';
        
        // Fallback to measured speed
        if (network.measuredSpeed) {
            const mbps = network.measuredSpeed.mbps;
            if (mbps < 0.5) return 'very-poor';
            if (mbps < 1) return 'poor';
            if (mbps < 5) return 'moderate';
            return 'good';
        }
        
        return 'unknown';
    }
    
    // Measure CPU performance
    async measureCPUPerformance() {
        const measurements = {
            jsExecutionSpeed: 0,
            domManipulationSpeed: 0,
            calculationSpeed: 0,
            renderingSpeed: 0
        };
        
        // Test 1: JavaScript execution speed
        const jsStart = performance.now();
        let result = 0;
        for (let i = 0; i < 1000000; i++) {
            result += Math.sqrt(i);
        }
        measurements.jsExecutionSpeed = performance.now() - jsStart;
        
        // Test 2: DOM manipulation speed
        const domStart = performance.now();
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < 100; i++) {
            const div = document.createElement('div');
            div.textContent = `Test ${i}`;
            fragment.appendChild(div);
        }
        measurements.domManipulationSpeed = performance.now() - domStart;
        
        // Test 3: Complex calculation
        const calcStart = performance.now();
        const fibonacci = (n) => n <= 1 ? n : fibonacci(n - 1) + fibonacci(n - 2);
        fibonacci(30);
        measurements.calculationSpeed = performance.now() - calcStart;
        
        // Test 4: Rendering performance
        const renderStart = performance.now();
        await new Promise(resolve => {
            requestAnimationFrame(() => {
                requestAnimationFrame(() => {
                    measurements.renderingSpeed = performance.now() - renderStart;
                    resolve();
                });
            });
        });
        
        // Calculate CPU score
        const cpuScore = this.calculateCPUScore(measurements);
        
        return {
            measurements,
            score: cpuScore,
            tier: cpuScore > 80 ? 'high' : cpuScore > 50 ? 'mid' : 'low'
        };
    }
    
    // Calculate CPU score
    calculateCPUScore(measurements) {
        // Baseline times (ms) for "good" performance
        const baselines = {
            jsExecutionSpeed: 50,
            domManipulationSpeed: 5,
            calculationSpeed: 100,
            renderingSpeed: 32
        };
        
        let score = 100;
        
        for (const [test, time] of Object.entries(measurements)) {
            const baseline = baselines[test];
            if (time > baseline) {
                // Reduce score based on how much slower than baseline
                const penalty = ((time - baseline) / baseline) * 20;
                score -= Math.min(penalty, 25); // Max 25 points penalty per test
            }
        }
        
        return Math.max(0, score);
    }
    
    // Analyze memory usage
    analyzeMemoryUsage() {
        const memory = {
            available: navigator.deviceMemory || 'unknown',
            usage: performance.memory ? {
                usedJSHeapSize: performance.memory.usedJSHeapSize,
                totalJSHeapSize: performance.memory.totalJSHeapSize,
                jsHeapSizeLimit: performance.memory.jsHeapSizeLimit,
                percentUsed: (performance.memory.usedJSHeapSize / performance.memory.jsHeapSizeLimit) * 100
            } : null,
            pressure: 'normal' // Would need Memory Pressure API
        };
        
        // Estimate memory pressure
        if (memory.usage) {
            if (memory.usage.percentUsed > 90) memory.pressure = 'critical';
            else if (memory.usage.percentUsed > 70) memory.pressure = 'high';
            else if (memory.usage.percentUsed > 50) memory.pressure = 'moderate';
        }
        
        return memory;
    }
    
    // Analyze touch performance
    analyzeTouchPerformance() {
        const touch = {
            supported: 'ontouchstart' in window,
            maxTouchPoints: navigator.maxTouchPoints || 0,
            // Check for touch event listeners
            hasPassiveListeners: false,
            hasTouchOptimizations: false,
            tapDelay: 300, // Default iOS tap delay
            issues: []
        };
        
        // Check for passive event listeners
        try {
            const opts = Object.defineProperty({}, 'passive', {
                get: () => { touch.hasPassiveListeners = true; }
            });
            window.addEventListener('test', null, opts);
            window.removeEventListener('test', null, opts);
        } catch (e) {}
        
        // Check for touch optimizations
        const styles = getComputedStyle(document.body);
        if (styles.touchAction && styles.touchAction !== 'auto') {
            touch.hasTouchOptimizations = true;
        }
        
        // Check for tap delay removal
        const viewport = document.querySelector('meta[name="viewport"]');
        if (viewport && viewport.content.includes('user-scalable=no')) {
            touch.tapDelay = 0;
        }
        
        // Identify issues
        if (!touch.hasPassiveListeners) {
            touch.issues.push({
                type: 'passive-listeners',
                message: 'Touch listeners not using passive option',
                impact: 'Scrolling may feel janky'
            });
        }
        
        if (touch.tapDelay > 0) {
            touch.issues.push({
                type: 'tap-delay',
                message: '300ms tap delay present',
                impact: 'UI feels sluggish'
            });
        }
        
        return touch;
    }
    
    // Analyze resource loading for mobile
    async analyzeResourceLoading() {
        const resources = performance.getEntriesByType('resource');
        const analysis = {
            total: resources.length,
            totalSize: 0,
            byType: {},
            critical: [],
            nonCritical: [],
            opportunities: []
        };
        
        // Group by type and analyze
        resources.forEach(resource => {
            const type = this.getResourceType(resource.name);
            
            if (!analysis.byType[type]) {
                analysis.byType[type] = {
                    count: 0,
                    size: 0,
                    avgLoadTime: 0
                };
            }
            
            analysis.byType[type].count++;
            analysis.byType[type].size += resource.transferSize || 0;
            analysis.byType[type].avgLoadTime += resource.duration;
            
            analysis.totalSize += resource.transferSize || 0;
            
            // Identify critical resources
            if (resource.startTime < 1000) { // First second
                analysis.critical.push({
                    url: resource.name,
                    type,
                    size: resource.transferSize,
                    duration: resource.duration
                });
            } else {
                analysis.nonCritical.push({
                    url: resource.name,
                    type,
                    size: resource.transferSize
                });
            }
        });
        
        // Calculate averages
        Object.values(analysis.byType).forEach(type => {
            type.avgLoadTime = type.avgLoadTime / type.count;
        });
        
        // Identify optimization opportunities
        if (analysis.byType.image && analysis.byType.image.size > 1000000) {
            analysis.opportunities.push({
                type: 'images',
                message: 'Images exceed 1MB total',
                savings: analysis.byType.image.size * 0.5
            });
        }
        
        if (analysis.byType.script && analysis.byType.script.size > 500000) {
            analysis.opportunities.push({
                type: 'javascript',
                message: 'JavaScript exceeds 500KB',
                savings: analysis.byType.script.size * 0.3
            });
        }
        
        return analysis;
    }
    
    // Get resource type
    getResourceType(url) {
        if (/\.(jpg|jpeg|png|gif|webp|svg|ico)/i.test(url)) return 'image';
        if (/\.(js|mjs)$/i.test(url)) return 'script';
        if (/\.(css)$/i.test(url)) return 'style';
        if (/\.(woff|woff2|ttf|otf|eot)/i.test(url)) return 'font';
        if (/\.(mp4|webm|ogg|mp3|wav)/i.test(url)) return 'media';
        return 'other';
    }
    
    // Analyze rendering performance
    analyzeRenderingPerformance() {
        const rendering = {
            fps: this.measureFPS(),
            paintMetrics: this.getPaintMetrics(),
            layoutMetrics: this.getLayoutMetrics(),
            animations: this.analyzeAnimations(),
            scrollPerformance: this.analyzeScrollPerformance()
        };
        
        return rendering;
    }
    
    // Measure FPS
    measureFPS() {
        // This would need to run over time
        // Simplified version
        let fps = 60;
        let lastTime = performance.now();
        let frames = 0;
        
        const measure = () => {
            frames++;
            const currentTime = performance.now();
            
            if (currentTime >= lastTime + 1000) {
                fps = Math.round((frames * 1000) / (currentTime - lastTime));
                frames = 0;
                lastTime = currentTime;
            }
        };
        
        // Would need to call measure() in RAF loop
        return fps;
    }
    
    // Get paint metrics
    getPaintMetrics() {
        const entries = performance.getEntriesByType('paint');
        const metrics = {};
        
        entries.forEach(entry => {
            metrics[entry.name] = entry.startTime;
        });
        
        // Get LCP
        const lcpEntries = performance.getEntriesByType('largest-contentful-paint');
        if (lcpEntries.length > 0) {
            metrics['largest-contentful-paint'] = lcpEntries[lcpEntries.length - 1].startTime;
        }
        
        return metrics;
    }
    
    // Get layout metrics
    getLayoutMetrics() {
        const observer = performance.getEntriesByType('layout-shift');
        let cls = 0;
        
        observer.forEach(entry => {
            if (!entry.hadRecentInput) {
                cls += entry.value;
            }
        });
        
        return {
            cumulativeLayoutShift: cls,
            layoutShifts: observer.length
        };
    }
    
    // Analyze animations
    analyzeAnimations() {
        const animations = {
            cssAnimations: 0,
            cssTransitions: 0,
            jsAnimations: 0,
            willChange: 0,
            transform3d: 0
        };
        
        // Check all elements
        const elements = document.querySelectorAll('*');
        elements.forEach(element => {
            const styles = getComputedStyle(element);
            
            if (styles.animation && styles.animation !== 'none') {
                animations.cssAnimations++;
            }
            
            if (styles.transition && styles.transition !== 'none') {
                animations.cssTransitions++;
            }
            
            if (styles.willChange && styles.willChange !== 'auto') {
                animations.willChange++;
            }
            
            if (styles.transform && styles.transform.includes('3d')) {
                animations.transform3d++;
            }
        });
        
        return animations;
    }
    
    // Analyze scroll performance
    analyzeScrollPerformance() {
        return {
            hasPassiveScroll: this.checkPassiveScroll(),
            hasSmoothScroll: getComputedStyle(document.documentElement).scrollBehavior === 'smooth',
            hasOverflowScrolling: this.checkOverflowScrolling(),
            scrollHeight: document.documentElement.scrollHeight,
            viewportHeight: window.innerHeight
        };
    }
    
    // Check passive scroll listeners
    checkPassiveScroll() {
        // Would need to check actual event listeners
        // This is a simplified check
        return true;
    }
    
    // Check overflow scrolling
    checkOverflowScrolling() {
        const elements = document.querySelectorAll('[style*="overflow"]');
        let hasWebkitOverflowScrolling = false;
        
        elements.forEach(element => {
            const style = getComputedStyle(element);
            if (style.webkitOverflowScrolling === 'touch') {
                hasWebkitOverflowScrolling = true;
            }
        });
        
        return hasWebkitOverflowScrolling;
    }
    
    // Analyze battery impact
    async analyzeBatteryImpact() {
        if (!navigator.getBattery) {
            return { supported: false };
        }
        
        try {
            const battery = await navigator.getBattery();
            
            return {
                supported: true,
                level: battery.level,
                charging: battery.charging,
                chargingTime: battery.chargingTime,
                dischargingTime: battery.dischargingTime,
                // Estimate impact based on resource usage
                estimatedImpact: this.estimateBatteryImpact()
            };
        } catch (e) {
            return { supported: false };
        }
    }
    
    // Estimate battery impact
    estimateBatteryImpact() {
        // Factors that impact battery
        const factors = {
            animations: document.querySelectorAll('[style*="animation"]').length,
            videos: document.querySelectorAll('video').length,
            highFrequencyTimers: 0, // Would need to track
            wakeLock: 'wakeLock' in navigator,
            gps: 'geolocation' in navigator
        };
        
        let impact = 'low';
        let score = 0;
        
        if (factors.animations > 10) score += 2;
        if (factors.videos > 0) score += 3;
        if (factors.wakeLock) score += 2;
        if (factors.gps) score += 1;
        
        if (score >= 5) impact = 'high';
        else if (score >= 3) impact = 'medium';
        
        return impact;
    }
    
    // Generate recommendations
    generateRecommendations(analysis) {
        const recommendations = [];
        
        // Device tier recommendations
        if (analysis.device.tier === 'low') {
            recommendations.push({
                priority: 'critical',
                category: 'performance',
                title: 'Low-end device detected',
                suggestions: [
                    'Reduce JavaScript bundle size below 100KB',
                    'Implement aggressive code splitting',
                    'Use CSS containment for complex layouts',
                    'Avoid complex animations'
                ]
            });
        }
        
        // Network recommendations
        if (analysis.network.quality === 'poor' || analysis.network.quality === 'very-poor') {
            recommendations.push({
                priority: 'high',
                category: 'network',
                title: 'Poor network conditions',
                suggestions: [
                    'Implement offline-first strategies',
                    'Use service workers for caching',
                    'Lazy load all non-critical resources',
                    'Reduce image quality for slow connections'
                ],
                code: `// Adaptive loading based on network
if (navigator.connection?.effectiveType === '2g' || 
    navigator.connection?.saveData) {
    // Load low-quality images
    img.src = img.dataset.lowSrc;
    // Skip non-essential features
    return;
}`
            });
        }
        
        // Memory recommendations
        if (analysis.memory.pressure === 'high' || analysis.memory.pressure === 'critical') {
            recommendations.push({
                priority: 'high',
                category: 'memory',
                title: 'High memory usage detected',
                suggestions: [
                    'Implement virtual scrolling for long lists',
                    'Remove DOM nodes when not visible',
                    'Clear unused variables and event listeners',
                    'Avoid memory leaks in SPAs'
                ]
            });
        }
        
        // Touch performance
        if (analysis.touch.issues.length > 0) {
            recommendations.push({
                priority: 'medium',
                category: 'interaction',
                title: 'Touch performance issues',
                issues: analysis.touch.issues,
                code: `// Use passive touch listeners
element.addEventListener('touchstart', handler, { passive: true });

// Remove tap delay
<meta name="viewport" content="width=device-width, initial-scale=1">

// CSS touch optimizations
.scrollable {
    -webkit-overflow-scrolling: touch;
    touch-action: pan-y;
}`
            });
        }
        
        // Resource loading
        if (analysis.resources.totalSize > 2000000) { // 2MB
            recommendations.push({
                priority: 'critical',
                category: 'resources',
                title: `Page size is ${(analysis.resources.totalSize / 1024 / 1024).toFixed(1)}MB`,
                impact: 'Extremely slow on mobile networks',
                suggestions: [
                    'Reduce image sizes and use modern formats',
                    'Implement code splitting',
                    'Remove unused CSS and JavaScript',
                    'Use resource hints wisely'
                ]
            });
        }
        
        // CPU performance
        if (analysis.cpu.tier === 'low') {
            recommendations.push({
                priority: 'high',
                category: 'cpu',
                title: 'CPU performance is limited',
                suggestions: [
                    'Use CSS transforms instead of JavaScript animations',
                    'Debounce scroll and resize handlers',
                    'Use Web Workers for heavy computations',
                    'Implement virtual scrolling'
                ],
                code: `// Debounce expensive operations
const debounce = (func, wait) => {
    let timeout;
    return (...args) => {
        clearTimeout(timeout);
        timeout = setTimeout(() => func(...args), wait);
    };
};

// Use CSS for animations
.animate {
    transform: translateX(0);
    transition: transform 0.3s ease;
}`
            });
        }
        
        return recommendations;
    }
}

// Run mobile performance analysis
const analyzer = new MobilePerformanceAnalyzer();
analyzer.analyzeMobilePerformance().then(analysis => {
    console.log('Mobile Performance Analysis:', analysis);
});

Network Condition Simulator

Simulate various mobile network conditions:

// Network Condition Simulator
class NetworkSimulator {
    constructor() {
        this.conditions = {
            '4g': { rtt: 50, downlink: 12, saveData: false },
            '3g': { rtt: 100, downlink: 1.5, saveData: false },
            '2g': { rtt: 300, downlink: 0.25, saveData: true },
            'slow-2g': { rtt: 400, downlink: 0.05, saveData: true },
            'offline': { rtt: Infinity, downlink: 0, saveData: true }
        };
        
        this.interceptors = new Map();
    }
    
    // Simulate network conditions
    async simulateNetwork(condition) {
        const config = this.conditions[condition];
        if (!config) {
            throw new Error(`Unknown network condition: ${condition}`);
        }
        
        console.log(`Simulating ${condition} network:`, config);
        
        // Override fetch to simulate network
        this.overrideFetch(config);
        
        // Simulate resource loading
        const results = await this.testResourceLoading(config);
        
        // Restore original fetch
        this.restoreFetch();
        
        return results;
    }
    
    // Override fetch to add delays
    overrideFetch(config) {
        const originalFetch = window.fetch;
        
        window.fetch = async (...args) => {
            const [url, options] = args;
            
            // Simulate RTT
            await this.delay(config.rtt);
            
            // Get response
            const response = await originalFetch(...args);
            const blob = await response.blob();
            
            // Simulate download time
            const downloadTime = this.calculateDownloadTime(
                blob.size,
                config.downlink
            );
            await this.delay(downloadTime);
            
            // Return new response with blob
            return new Response(blob, {
                status: response.status,
                statusText: response.statusText,
                headers: response.headers
            });
        };
        
        this.interceptors.set('fetch', originalFetch);
    }
    
    // Restore original fetch
    restoreFetch() {
        if (this.interceptors.has('fetch')) {
            window.fetch = this.interceptors.get('fetch');
            this.interceptors.delete('fetch');
        }
    }
    
    // Calculate download time
    calculateDownloadTime(bytes, mbps) {
        const bits = bytes * 8;
        const bps = mbps * 1000000;
        return (bits / bps) * 1000; // Convert to milliseconds
    }
    
    // Delay helper
    delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    // Test resource loading under conditions
    async testResourceLoading(config) {
        const resources = [
            { url: '/api/data', size: 50000 },    // 50KB API response
            { url: '/image.jpg', size: 200000 },  // 200KB image
            { url: '/script.js', size: 100000 },  // 100KB script
            { url: '/style.css', size: 30000 }    // 30KB stylesheet
        ];
        
        const results = {
            condition: config,
            resources: [],
            totalTime: 0,
            recommendations: []
        };
        
        for (const resource of resources) {
            const startTime = performance.now();
            
            // Simulate loading
            const downloadTime = this.calculateDownloadTime(
                resource.size,
                config.downlink
            );
            
            const totalTime = config.rtt + downloadTime;
            await this.delay(totalTime);
            
            const endTime = performance.now();
            const actualTime = endTime - startTime;
            
            results.resources.push({
                url: resource.url,
                size: resource.size,
                estimatedTime: totalTime,
                actualTime: actualTime,
                wouldTimeout: totalTime > 30000 // 30s timeout
            });
            
            results.totalTime += actualTime;
        }
        
        // Generate recommendations
        results.recommendations = this.generateNetworkRecommendations(results);
        
        return results;
    }
    
    // Generate network-specific recommendations
    generateNetworkRecommendations(results) {
        const recommendations = [];
        const totalSize = results.resources.reduce((sum, r) => sum + r.size, 0);
        
        if (results.totalTime > 10000) { // 10 seconds
            recommendations.push({
                type: 'critical-resources',
                message: 'Page takes over 10 seconds to load key resources',
                solution: 'Inline critical CSS, reduce JavaScript, optimize images'
            });
        }
        
        const timeouts = results.resources.filter(r => r.wouldTimeout);
        if (timeouts.length > 0) {
            recommendations.push({
                type: 'timeout-risk',
                message: `${timeouts.length} resources at risk of timeout`,
                resources: timeouts.map(r => r.url),
                solution: 'Implement progressive loading and timeouts'
            });
        }
        
        if (totalSize > 500000 && results.condition.saveData) {
            recommendations.push({
                type: 'save-data',
                message: 'User has Save-Data enabled but page is large',
                solution: 'Respect Save-Data header and serve minimal resources',
                code: `if (navigator.connection?.saveData) {
    // Skip non-essential resources
    document.querySelectorAll('img[data-lazy]').forEach(img => {
        img.removeAttribute('data-lazy');
    });
}`
            });
        }
        
        return recommendations;
    }
}

// Test different network conditions
const simulator = new NetworkSimulator();

// Test 3G network
simulator.simulateNetwork('3g').then(results => {
    console.log('3G Network Simulation:', results);
});

Mobile-Specific Optimization Tool

Generate mobile-optimized code:

// Mobile Optimization Generator
class MobileOptimizer {
    constructor() {
        this.optimizations = [];
    }
    
    // Generate comprehensive mobile optimizations
    generateOptimizations() {
        return {
            viewport: this.generateViewportOptimizations(),
            touch: this.generateTouchOptimizations(),
            performance: this.generatePerformanceOptimizations(),
            images: this.generateImageOptimizations(),
            fonts: this.generateFontOptimizations(),
            offline: this.generateOfflineStrategy(),
            adaptive: this.generateAdaptiveLoading()
        };
    }
    
    // Viewport optimizations
    generateViewportOptimizations() {
        return {
            meta: `<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">`,
            css: `/* Safe area insets for notched devices */
:root {
    --safe-area-inset-top: env(safe-area-inset-top);
    --safe-area-inset-right: env(safe-area-inset-right);
    --safe-area-inset-bottom: env(safe-area-inset-bottom);
    --safe-area-inset-left: env(safe-area-inset-left);
}

.app-header {
    padding-top: calc(1rem + var(--safe-area-inset-top));
}

.app-footer {
    padding-bottom: calc(1rem + var(--safe-area-inset-bottom));
}

/* Prevent horizontal scroll */
html, body {
    overflow-x: hidden;
    max-width: 100%;
}

/* Responsive utilities */
.mobile-only {
    display: block;
}

.desktop-only {
    display: none;
}

@media (min-width: 768px) {
    .mobile-only {
        display: none;
    }
    
    .desktop-only {
        display: block;
    }
}`,
            javascript: `// Handle viewport changes
let vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', \`\${vh}px\`);

window.addEventListener('resize', () => {
    vh = window.innerHeight * 0.01;
    document.documentElement.style.setProperty('--vh', \`\${vh}px\`);
});

// Handle orientation changes
window.addEventListener('orientationchange', () => {
    setTimeout(() => {
        window.scrollTo(0, 1);
    }, 500);
});`
        };
    }
    
    // Touch optimizations
    generateTouchOptimizations() {
        return {
            css: `/* Remove tap highlight */
* {
    -webkit-tap-highlight-color: transparent;
}

/* Smooth scrolling */
.scrollable {
    -webkit-overflow-scrolling: touch;
    overflow-scrolling: touch;
}

/* Prevent callout on long press */
.no-select {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
}

/* Touch-friendly buttons */
.touch-target {
    min-height: 44px;
    min-width: 44px;
    padding: 12px;
    touch-action: manipulation;
}

/* Improve touch responsiveness */
.touchable {
    touch-action: manipulation;
    cursor: pointer;
}`,
            javascript: `// Fast click implementation
class FastClick {
    constructor(element) {
        this.element = element;
        this.moved = false;
        
        element.addEventListener('touchstart', this.onTouchStart.bind(this), { passive: true });
        element.addEventListener('touchmove', this.onTouchMove.bind(this), { passive: true });
        element.addEventListener('touchend', this.onTouchEnd.bind(this));
    }
    
    onTouchStart(e) {
        this.moved = false;
        this.startX = e.touches[0].clientX;
        this.startY = e.touches[0].clientY;
    }
    
    onTouchMove(e) {
        const x = e.touches[0].clientX;
        const y = e.touches[0].clientY;
        
        // Moved more than 10px
        if (Math.abs(x - this.startX) > 10 || Math.abs(y - this.startY) > 10) {
            this.moved = true;
        }
    }
    
    onTouchEnd(e) {
        if (!this.moved) {
            e.preventDefault();
            // Trigger click
            const clickEvent = new MouseEvent('click', {
                bubbles: true,
                cancelable: true,
                view: window
            });
            this.element.dispatchEvent(clickEvent);
        }
    }
}

// Apply to all buttons
document.querySelectorAll('button, .btn').forEach(btn => {
    new FastClick(btn);
});

// Passive event listeners for scroll performance
document.addEventListener('touchstart', handler, { passive: true });
document.addEventListener('touchmove', handler, { passive: true });
document.addEventListener('wheel', handler, { passive: true });`
        };
    }
    
    // Performance optimizations
    generatePerformanceOptimizations() {
        return {
            css: `/* Use GPU acceleration wisely */
.gpu-accelerated {
    transform: translateZ(0);
    will-change: transform;
}

/* Contain layout calculations */
.container {
    contain: layout style paint;
}

/* Reduce paint areas */
.static-content {
    contain: strict;
}

/* Font loading optimization */
@font-face {
    font-family: 'CustomFont';
    src: url('font.woff2') format('woff2');
    font-display: swap;
}`,
            javascript: `// Intersection Observer for lazy loading
const imageObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            img.classList.add('loaded');
            imageObserver.unobserve(img);
        }
    });
}, {
    rootMargin: '50px 0px',
    threshold: 0.01
});

document.querySelectorAll('img[data-src]').forEach(img => {
    imageObserver.observe(img);
});

// Request Idle Callback for non-critical tasks
const deferredTasks = [];

function runDeferredTasks(deadline) {
    while (deadline.timeRemaining() > 0 && deferredTasks.length > 0) {
        const task = deferredTasks.shift();
        task();
    }
    
    if (deferredTasks.length > 0) {
        requestIdleCallback(runDeferredTasks);
    }
}

if ('requestIdleCallback' in window) {
    requestIdleCallback(runDeferredTasks);
}

// Debounce expensive operations
const debounce = (func, wait) => {
    let timeout;
    return (...args) => {
        clearTimeout(timeout);
        timeout = setTimeout(() => func.apply(this, args), wait);
    };
};

// Throttle scroll events
const throttle = (func, limit) => {
    let inThrottle;
    return (...args) => {
        if (!inThrottle) {
            func.apply(this, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
        }
    };
};

window.addEventListener('scroll', throttle(handleScroll, 100));
window.addEventListener('resize', debounce(handleResize, 300));`
        };
    }
    
    // Image optimizations
    generateImageOptimizations() {
        return {
            html: `<!-- Responsive images with WebP -->
<picture>
    <source 
        media="(max-width: 640px)"
        srcset="image-mobile.webp 1x, image-mobile@2x.webp 2x"
        type="image/webp"
    >
    <source 
        media="(max-width: 640px)"
        srcset="image-mobile.jpg 1x, image-mobile@2x.jpg 2x"
    >
    <source 
        srcset="image-desktop.webp"
        type="image/webp"
    >
    <img 
        src="image-desktop.jpg" 
        alt="Description"
        loading="lazy"
        decoding="async"
        width="800"
        height="600"
    >
</picture>`,
            javascript: `// Adaptive image loading based on network
class AdaptiveImageLoader {
    constructor() {
        this.connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
    }
    
    getImageSrc(element) {
        const sources = {
            high: element.dataset.srcHigh,
            medium: element.dataset.srcMedium,
            low: element.dataset.srcLow
        };
        
        // Check save data
        if (this.connection?.saveData) {
            return sources.low;
        }
        
        // Check connection type
        const effectiveType = this.connection?.effectiveType;
        
        switch(effectiveType) {
            case '4g':
                return sources.high;
            case '3g':
                return sources.medium;
            case '2g':
            case 'slow-2g':
                return sources.low;
            default:
                // Check device pixel ratio
                if (window.devicePixelRatio > 2) {
                    return sources.high;
                }
                return sources.medium;
        }
    }
    
    loadImage(img) {
        const src = this.getImageSrc(img);
        if (src) {
            img.src = src;
        }
    }
}

const imageLoader = new AdaptiveImageLoader();
document.querySelectorAll('img[data-adaptive]').forEach(img => {
    imageLoader.loadImage(img);
});`
        };
    }
    
    // Font optimizations
    generateFontOptimizations() {
        return {
            css: `/* System font stack for instant rendering */
.system-font {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", 
                 Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", 
                 "Droid Sans", "Helvetica Neue", Arial, sans-serif;
}

/* Variable fonts for smaller file size */
@font-face {
    font-family: 'VariableFont';
    src: url('font-variable.woff2') format('woff2-variations');
    font-weight: 100 900;
    font-display: swap;
}

/* Subset fonts for faster loading */
@font-face {
    font-family: 'CustomFont';
    src: url('font-subset.woff2') format('woff2');
    unicode-range: U+0020-007E; /* Basic Latin */
    font-display: fallback;
}`,
            javascript: `// Font loading strategy
if ('fonts' in document) {
    // Modern browsers
    document.fonts.load('1rem CustomFont').then(() => {
        document.documentElement.classList.add('fonts-loaded');
    }).catch(() => {
        // Fallback to system fonts
        document.documentElement.classList.add('fonts-failed');
    });
} else {
    // Older browsers - use Font Face Observer or similar
    const font = new FontFaceObserver('CustomFont');
    font.load().then(() => {
        document.documentElement.classList.add('fonts-loaded');
    });
}`
        };
    }
    
    // Offline strategy
    generateOfflineStrategy() {
        return {
            serviceWorker: `// Service Worker for offline support
const CACHE_NAME = 'app-v1';
const urlsToCache = [
    '/',
    '/styles/main.css',
    '/scripts/main.js',
    '/offline.html'
];

// Install event
self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(CACHE_NAME)
            .then(cache => cache.addAll(urlsToCache))
    );
});

// Fetch event with network-first strategy
self.addEventListener('fetch', event => {
    event.respondWith(
        fetch(event.request)
            .then(response => {
                // Clone the response
                const responseToCache = response.clone();
                
                caches.open(CACHE_NAME)
                    .then(cache => {
                        cache.put(event.request, responseToCache);
                    });
                
                return response;
            })
            .catch(() => {
                return caches.match(event.request)
                    .then(response => {
                        if (response) {
                            return response;
                        }
                        // Return offline page for navigation requests
                        if (event.request.mode === 'navigate') {
                            return caches.match('/offline.html');
                        }
                    });
            })
    );
});`,
            registration: `// Register service worker
if ('serviceWorker' in navigator) {
    window.addEventListener('load', () => {
        navigator.serviceWorker.register('/sw.js')
            .then(registration => {
                console.log('SW registered:', registration);
            })
            .catch(error => {
                console.log('SW registration failed:', error);
            });
    });
}

// Offline detection
window.addEventListener('online', () => {
    document.body.classList.remove('offline');
});

window.addEventListener('offline', () => {
    document.body.classList.add('offline');
});`
        };
    }
    
    // Adaptive loading strategy
    generateAdaptiveLoading() {
        return `// Adaptive Loading Manager
class AdaptiveLoader {
    constructor() {
        this.deviceMemory = navigator.deviceMemory || 4;
        this.connection = navigator.connection;
        this.saveData = this.connection?.saveData || false;
        this.cores = navigator.hardwareConcurrency || 4;
    }
    
    getDeviceCapabilities() {
        let score = 0;
        
        // Memory score
        if (this.deviceMemory >= 8) score += 3;
        else if (this.deviceMemory >= 4) score += 2;
        else score += 1;
        
        // CPU score
        if (this.cores >= 8) score += 3;
        else if (this.cores >= 4) score += 2;
        else score += 1;
        
        // Network score
        const effectiveType = this.connection?.effectiveType;
        if (effectiveType === '4g') score += 3;
        else if (effectiveType === '3g') score += 2;
        else score += 1;
        
        return {
            score,
            tier: score >= 7 ? 'high' : score >= 4 ? 'medium' : 'low',
            memory: this.deviceMemory,
            cores: this.cores,
            network: effectiveType,
            saveData: this.saveData
        };
    }
    
    async loadFeature(feature) {
        const capabilities = this.getDeviceCapabilities();
        
        // Don't load heavy features on low-end devices
        if (capabilities.tier === 'low' && feature.heavy) {
            console.log(\`Skipping \${feature.name} on low-end device\`);
            return null;
        }
        
        // Use lighter versions when appropriate
        if (capabilities.saveData || capabilities.network === '2g') {
            if (feature.lite) {
                return import(feature.lite);
            }
        }
        
        // Load the full feature
        return import(feature.full);
    }
    
    // Example usage
    async initializeApp() {
        const capabilities = this.getDeviceCapabilities();
        
        // Core features - always load
        await import('./core.js');
        
        // Conditional features based on device
        if (capabilities.tier !== 'low') {
            // Animation library
            this.loadFeature({
                name: 'animations',
                full: './animations-full.js',
                lite: './animations-lite.js',
                heavy: true
            });
            
            // Rich text editor
            this.loadFeature({
                name: 'editor',
                full: './editor-full.js',
                lite: './editor-lite.js',
                heavy: true
            });
        }
        
        // Analytics - load in idle time
        if ('requestIdleCallback' in window) {
            requestIdleCallback(() => {
                import('./analytics.js');
            });
        }
    }
}

const loader = new AdaptiveLoader();
loader.initializeApp();`;
    }
}

// Generate mobile optimizations
const optimizer = new MobileOptimizer();
const optimizations = optimizer.generateOptimizations();
console.log('Mobile Optimizations:', optimizations);

Best Practices for Mobile Performance

  1. Respect Data Saver: Check navigator.connection.saveData
  2. Adaptive Loading: Serve different assets based on device capability
  3. Touch-Friendly: Minimum 44x44px touch targets
  4. Avoid Jank: Use passive listeners and will-change carefully
  5. Lazy Everything: Images, fonts, non-critical JavaScript
  6. Offline First: Service workers for resilient apps
  7. Battery Conscious: Minimize animations and wake locks
  8. Test on Real Devices: Emulators miss real-world constraints
  9. Monitor Field Data: Use RUM to track actual performance
  10. Progressive Enhancement: Core functionality works everywhere

Common Mobile Performance Mistakes

  • Desktop-First Development: Mobile as an afterthought
  • Ignoring Network Variability: Testing only on WiFi
  • Heavy Frameworks: 200KB+ of JavaScript for simple sites
  • No Touch Optimization: 300ms tap delays, non-passive listeners
  • Memory Leaks: Not cleaning up in SPAs
  • Viewport Issues: Horizontal scroll, text too small
  • Battery Drain: Continuous animations, wake locks
  • Large Images: Serving desktop images to mobile
  • Blocking Resources: Render-blocking CSS/JS
  • No Offline Support: White screen when connection drops

Remember: Mobile performance isn't about making a desktop site smaller—it's about building for intermittent connectivity, limited CPU, and battery constraints from the ground up.