/** * 计算并保存网页加载时间 * @author zhaoxianlie */ window.pagetimingContentScript = function () { /** * Navigation Timing API helpers * timing.getTimes(); **/ window.timing = window.timing || { /** * Outputs extended measurements using Navigation Timing API * @param Object opts Options (simple (bool) - opts out of full data view) * @return Object measurements */ getTimes: function(opts) { var performance = window.performance || window.webkitPerformance || window.msPerformance || window.mozPerformance; if (performance === undefined) { return false; } var timing = performance.timing; var api = {}; opts = opts || {}; if (timing) { if(opts && !opts.simple) { for (var k in timing) { if(isNumeric(timing[k])) { api[k] = parseFloat(timing[k]); } } } // Time to first paint if (api.firstPaint === undefined) { var firstPaint = 0; // IE if (typeof timing.msFirstPaint === 'number') { firstPaint = timing.msFirstPaint; api.firstPaintTime = firstPaint - timing.navigationStart; } else if (performance.getEntriesByName !== undefined) { var firstPaintPerformanceEntry = performance.getEntriesByName('first-paint'); if (firstPaintPerformanceEntry.length === 1) { var firstPaintTime = firstPaintPerformanceEntry[0].startTime; firstPaint = performance.timeOrigin + firstPaintTime; api.firstPaintTime = firstPaintTime; } } if (opts && !opts.simple) { api.firstPaint = firstPaint; } } // Total time from start to load api.loadTime = timing.loadEventEnd - timing.fetchStart; // Time spent constructing the DOM tree api.domReadyTime = timing.domComplete - timing.domInteractive; // Time consumed preparing the new page api.readyStart = timing.fetchStart - timing.navigationStart; // Time spent during redirection api.redirectTime = timing.redirectEnd - timing.redirectStart; // AppCache api.appcacheTime = timing.domainLookupStart - timing.fetchStart; // Time spent unloading documents api.unloadEventTime = timing.unloadEventEnd - timing.unloadEventStart; // DNS query time api.lookupDomainTime = timing.domainLookupEnd - timing.domainLookupStart; // TCP connection time api.connectTime = timing.connectEnd - timing.connectStart; // Time spent during the request api.requestTime = timing.responseEnd - timing.requestStart; // Request to completion of the DOM loading api.initDomTreeTime = timing.domInteractive - timing.responseEnd; // Load event time api.loadEventTime = timing.loadEventEnd - timing.loadEventStart; } return api; }, /** * Uses console.table() to print a complete table of timing information * @param Object opts Options (simple (bool) - opts out of full data view) */ printTable: function(opts) { var table = {}; var data = this.getTimes(opts) || {}; Object.keys(data).sort().forEach(function(k) { table[k] = { ms: data[k], s: +((data[k] / 1000).toFixed(2)) }; }); console.table(table); }, /** * Uses console.table() to print a summary table of timing information */ printSimpleTable: function() { this.printTable({simple: true}); } }; function isNumeric(n) { return !isNaN(parseFloat(n)) && isFinite(n); } // 获取资源加载性能数据 function getResourceTiming() { const resources = performance.getEntriesByType('resource'); return resources.map(resource => ({ name: resource.name, entryType: resource.entryType, startTime: resource.startTime, duration: resource.duration, transferSize: resource.transferSize, decodedBodySize: resource.decodedBodySize, encodedBodySize: resource.encodedBodySize, dnsTime: resource.domainLookupEnd - resource.domainLookupStart, tcpTime: resource.connectEnd - resource.connectStart, ttfb: resource.responseStart - resource.requestStart, downloadTime: resource.responseEnd - resource.responseStart })); } // 获取核心Web指标 function getCoreWebVitals() { return new Promise(resolve => { let webVitals = {}; // LCP (Largest Contentful Paint) new PerformanceObserver((entryList) => { const entries = entryList.getEntries(); const lastEntry = entries[entries.length - 1]; webVitals.lcp = lastEntry.renderTime || lastEntry.loadTime; }).observe({entryTypes: ['largest-contentful-paint']}); // FID (First Input Delay) new PerformanceObserver((entryList) => { const firstInput = entryList.getEntries()[0]; if (firstInput) { webVitals.fid = firstInput.processingTime; webVitals.firstInputTime = firstInput.startTime; } }).observe({entryTypes: ['first-input']}); // CLS (Cumulative Layout Shift) let clsValue = 0; new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { if (!entry.hadRecentInput) { clsValue += entry.value; } } webVitals.cls = clsValue; }).observe({entryTypes: ['layout-shift']}); setTimeout(() => resolve(webVitals), 3000); }); } // 获取性能指标 function getPerformanceMetrics() { if (window.performance.memory) { return { jsHeapSizeLimit: performance.memory.jsHeapSizeLimit, totalJSHeapSize: performance.memory.totalJSHeapSize, usedJSHeapSize: performance.memory.usedJSHeapSize }; } return null; } // 监控长任务 function observeLongTasks() { const longTasks = []; new PerformanceObserver((list) => { for (const entry of list.getEntries()) { longTasks.push({ duration: entry.duration, startTime: entry.startTime, name: entry.name }); } }).observe({entryTypes: ['longtask']}); return longTasks; } // 获取网络信息 function getNetworkInfo() { if ('connection' in navigator) { const connection = navigator.connection; return { effectiveType: connection.effectiveType, downlink: connection.downlink, rtt: connection.rtt, saveData: connection.saveData }; } return null; } // 创建进度提示框 function createProgressTip() { const tipContainer = document.createElement('div'); tipContainer.id = 'fe-helper-timing-tip'; tipContainer.style.cssText = ` position: fixed; top: 20px; right: 20px; background: rgba(0, 0, 0, 0.8); color: white; padding: 15px 20px; border-radius: 8px; font-size: 14px; z-index: 999999; box-shadow: 0 2px 10px rgba(0,0,0,0.2); transition: opacity 0.3s; `; document.body.appendChild(tipContainer); return tipContainer; } // 更新提示框内容 function updateProgressTip(message, progress) { const tipContainer = document.getElementById('fe-helper-timing-tip') || createProgressTip(); tipContainer.innerHTML = `