Browse Source

upgrade website performance tools

zxlie 6 months ago
parent
commit
ee2b829583

+ 3 - 3
apps/background/tools.js

@@ -223,13 +223,13 @@ let toolMap = {
         }]
     },
     'page-timing': {
-        name: '网页性能检测',
-        tips: '检测网页加载性能,包括握手、响应、渲染等各阶段耗时,同时提供Response Headers以便分析',
+        name: '网站性能优化',
+        tips: '全面分析网页性能指标,包括核心Web指标(LCP/FID/CLS)、资源加载性能、内存使用、长任务监控等,并提供针对性的优化建议',
         contentScriptJs: true,
         noPage: true,
         menuConfig: [{
             icon: 'Σ',
-            text: '网页性能检测'
+            text: '网站性能优化'
         }]
     },
     'excel2json': {

+ 1 - 1
apps/options/market.js

@@ -4,7 +4,7 @@ import Settings from './settings.js';
 
 // 工具分类定义
 const TOOL_CATEGORIES = [
-    { key: 'dev', name: '开发工具类', tools: ['json-format', 'json-diff', 'code-beautify', 'code-compress', 'postman', 'websocket', 'regexp'] },
+    { key: 'dev', name: '开发工具类', tools: ['json-format', 'json-diff', 'code-beautify', 'code-compress', 'postman', 'websocket', 'regexp','page-timing'] },
     { key: 'encode', name: '编解码转换类', tools: ['en-decode', 'trans-radix', 'timestamp', 'trans-color'] },
     { key: 'image', name: '图像处理类', tools: ['qr-code', 'image-base64', 'svg-converter', 'chart-maker' ,'screenshot', 'color-picker'] },
     { key: 'productivity', name: '效率工具类', tools: ['aiagent', 'sticky-notes', 'html2markdown', 'page-monkey'] },

+ 268 - 21
apps/page-timing/content-script.js

@@ -4,40 +4,283 @@
  */
 window.pagetimingContentScript = function () {
 
-    let __importScript = (filename) => {
-        pleaseLetJsLoaded = 100;
-        let url = filename;
+    /**
+     * 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 (location.protocol === 'chrome-extension:' || chrome.runtime && chrome.runtime.getURL) {
-            url = chrome.runtime.getURL('page-timing/' + filename);
-        }
-        fetch(url).then(resp => resp.text()).then(jsText => {
-            if(window.evalCore && window.evalCore.getEvalInstance){
-                return window.evalCore.getEvalInstance(window)(jsText);
+            if (performance === undefined) {
+                return false;
             }
-            let el = document.createElement('script');
-            el.textContent = jsText;
-            document.head.appendChild(el);
-        });
+
+            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});
+        }
     };
 
-    __importScript('timing.js');
+    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 = `
+            <div>${message}</div>
+            ${progress ? `<div style="margin-top:8px;background:rgba(255,255,255,0.2);height:2px;border-radius:1px">
+                <div style="width:${progress}%;height:100%;background:#4CAF50;border-radius:1px"></div>
+            </div>` : ''}
+        `;
+    }
+
+    // 移除提示框
+    function removeProgressTip() {
+        const tipContainer = document.getElementById('fe-helper-timing-tip');
+        if (tipContainer) {
+            tipContainer.style.opacity = '0';
+            setTimeout(() => tipContainer.remove(), 300);
+        }
+    }
 
     window.pagetimingNoPage = function() {
+        updateProgressTip('正在收集页面基础信息...', 20);
 
         let wpoInfo = {
             pageInfo: {
                 title: document.title,
                 url: location.href
             },
-            time: window.timing.getTimes({simple: true})
+            time: window.timing.getTimes({simple: true}),
+            resources: getResourceTiming(),
+            networkInfo: getNetworkInfo(),
+            performanceMetrics: getPerformanceMetrics(),
+            longTasks: []
         };
 
+        updateProgressTip('正在监控页面性能...', 40);
+        // 初始化长任务监控
+        const longTasksMonitor = observeLongTasks();
+
         let sendWpoInfo = function () {
-            chrome.runtime.sendMessage({
-                type: 'fh-dynamic-any-thing',
-                thing: 'set-page-timing-data',
-                wpoInfo: wpoInfo
+            updateProgressTip('正在处理性能数据...', 60);
+            // 合并长任务数据
+            wpoInfo.longTasks = longTasksMonitor;
+
+            // 获取核心Web指标
+            getCoreWebVitals().then(webVitals => {
+                updateProgressTip('正在完成数据采集...', 80);
+                wpoInfo.webVitals = webVitals;
+                
+                chrome.runtime.sendMessage({
+                    type: 'fh-dynamic-any-thing',
+                    thing: 'set-page-timing-data',
+                    wpoInfo: wpoInfo
+                }, () => {
+                    updateProgressTip('数据采集完成!', 100);
+                    setTimeout(removeProgressTip, 1000);
+                });
             });
         };
 
@@ -45,6 +288,7 @@ window.pagetimingContentScript = function () {
             if (wpoInfo.header && wpoInfo.time && wpoInfo.pageInfo) {
                 sendWpoInfo();
             } else {
+                updateProgressTip('正在获取页面请求头信息...', 50);
                 fetch(location.href).then(resp => {
                     let header = {};
                     for (let pair of resp.headers.entries()) {
@@ -54,7 +298,11 @@ window.pagetimingContentScript = function () {
                 }).then(header => {
                     wpoInfo.header = header;
                     sendWpoInfo();
-                }).catch(console.log);
+                }).catch(error => {
+                    console.log(error);
+                    updateProgressTip('获取请求头信息失败,继续其他数据采集...', 50);
+                    sendWpoInfo();
+                });
             }
         };
 
@@ -69,5 +317,4 @@ window.pagetimingContentScript = function () {
 
         detect();
     };
-
 };

+ 391 - 109
apps/page-timing/index.css

@@ -1,109 +1,391 @@
-@import url("../static/css/bootstrap.min.css");
-
-body {
-    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
-    font-size: 14px;
-    color: #2c3e50;
-    margin: 0 auto;
-    background-color: #f5f7fa;
-}
-
-.panel-body.mod-json {
-    padding: 20px;
-}
-
-#pageInfo {
-    padding: 15px 20px;
-    line-height: 1.6;
-    background: #fff;
-    border-radius: 8px;
-    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
-    margin-bottom: 20px;
-}
-
-#pageInfo span {
-    color: #5c6f8a;
-    word-wrap: break-word;
-    font-size: 14px;
-    margin-left: 8px;
-}
-
-#pageLoadTime, #pageHeaderInfo {
-    background: #fff;
-    border-radius: 8px;
-    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
-    padding: 20px;
-    margin-bottom: 20px;
-}
-
-#pageLoadTime {
-    width: 48%;
-    float: left;
-}
-
-#pageHeaderInfo {
-    width: 48%;
-    float: right;
-    margin-left: 0;
-}
-
-h3 {
-    color: #2c3e50;
-    font-size: 18px;
-    margin-bottom: 20px;
-    font-weight: 600;
-}
-
-.table {
-    width: 100%;
-    margin-bottom: 0;
-}
-
-.table > thead > tr > th {
-    background: #f8fafc;
-    color: #2c3e50;
-    font-weight: 600;
-    border-bottom: 2px solid #edf2f7;
-    padding: 12px 15px;
-}
-
-.table > tbody > tr > td {
-    padding: 12px 15px;
-    border-bottom: 1px solid #edf2f7;
-    color: #4a5568;
-}
-
-.table > tbody > tr:last-child > td {
-    border-bottom: none;
-}
-
-.table-hover > tbody > tr:hover {
-    background-color: #f8fafc;
-}
-
-.table-striped > tbody > tr:nth-of-type(odd) {
-    background-color: #f9fafb;
-}
-
-/* 响应式布局 */
-@media (max-width: 768px) {
-    #pageLoadTime, #pageHeaderInfo {
-        width: 100%;
-        float: none;
-    }
-    
-    #pageHeaderInfo {
-        margin-left: 0;
-    }
-    
-    .table-responsive {
-        border: none;
-    }
-}
-
-/* 清除浮动 */
-.row:after {
-    content: "";
-    display: table;
-    clear: both;
-}
+@import url("../static/css/bootstrap.min.css");
+
+body {
+    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+    font-size: 13px;
+    color: #2c3e50;
+    margin: 0 auto;
+    background-color: #f5f7fa;
+}
+
+.panel-body.mod-json {
+    padding: 15px;
+}
+
+/* 页面基本信息样式 */
+.info-card {
+    background: #fff;
+    border-radius: 8px;
+    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+    padding: 15px;
+    margin-bottom: 20px;
+}
+
+.info-item {
+    margin-bottom: 8px;
+    display: flex;
+    align-items: center;
+}
+
+.info-item label {
+    color: #666;
+    min-width: 100px;
+    margin-bottom: 0;
+}
+
+.info-item span {
+    color: #2c3e50;
+    word-break: break-all;
+}
+
+.update-time {
+    margin-top: 10px;
+    padding-top: 10px;
+    border-top: 1px solid #eee;
+    color: #999;
+    font-size: 12px;
+    text-align: right;
+}
+
+/* 性能概览区域样式 */
+.performance-overview {
+    margin-bottom: 20px;
+}
+
+.performance-card {
+    background: #fff;
+    border-radius: 8px;
+    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+    padding: 15px;
+    margin-bottom: 15px;
+}
+
+/* 核心Web指标网格布局 */
+.metrics-grid {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 15px;
+    margin-top: 15px;
+}
+
+.metric-item {
+    padding: 15px;
+    border-radius: 6px;
+    text-align: center;
+    background: #f8f9fa;
+    transition: all 0.3s ease;
+}
+
+.metric-item.text-success {
+    background: #d4edda;
+    border: 1px solid #c3e6cb;
+}
+
+.metric-item.text-warning {
+    background: #fff3cd;
+    border: 1px solid #ffeeba;
+}
+
+.metric-item.text-danger {
+    background: #f8d7da;
+    border: 1px solid #f5c6cb;
+}
+
+.metric-value {
+    font-size: 24px;
+    font-weight: 600;
+    margin-bottom: 5px;
+}
+
+.metric-label {
+    font-size: 13px;
+    color: #666;
+    margin-bottom: 5px;
+}
+
+.metric-status {
+    font-size: 12px;
+    font-weight: 500;
+}
+
+/* 页面加载时间和内存使用布局 */
+.flex-container {
+    display: flex;
+    gap: 15px;
+    margin-top: 15px;
+}
+
+.half-width {
+    flex: 1;
+    min-width: 0;
+}
+
+/* 时间列表样式 */
+.timing-list {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+}
+
+.timing-item {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 8px;
+    background: #f8f9fa;
+    border-radius: 4px;
+}
+
+.timing-label {
+    color: #666;
+}
+
+.timing-value {
+    font-weight: 500;
+    color: #2c3e50;
+}
+
+/* 内存使用样式 */
+.memory-usage {
+    padding: 10px;
+}
+
+.memory-item {
+    margin-bottom: 10px;
+}
+
+.memory-label {
+    color: #666;
+    margin-bottom: 5px;
+}
+
+.memory-value {
+    font-weight: 500;
+    color: #2c3e50;
+}
+
+.memory-bar {
+    height: 20px;
+    background: #f8f9fa;
+    border-radius: 10px;
+    position: relative;
+    overflow: hidden;
+}
+
+.used-bar {
+    position: absolute;
+    height: 100%;
+    background: #4CAF50;
+    border-radius: 10px;
+    transition: width 0.3s ease;
+}
+
+.total-bar {
+    position: absolute;
+    height: 100%;
+    background: rgba(76, 175, 80, 0.3);
+    border-radius: 10px;
+    transition: width 0.3s ease;
+}
+
+/* 详细信息区域样式 */
+.detail-card {
+    margin-bottom: 20px;
+    border: 1px solid #e0e0e0;
+    border-radius: 8px;
+    overflow: hidden;
+}
+
+.card-header {
+    padding: 15px;
+    background: #f8f9fa;
+    cursor: pointer;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    border-bottom: 1px solid #eee;
+}
+
+.card-header h3 {
+    margin: 0;
+    font-size: 15px;
+    font-weight: 600;
+}
+
+.toggle-icon {
+    font-size: 12px;
+    transition: transform 0.3s ease;
+}
+
+.card-header[aria-expanded="true"] .toggle-icon {
+    transform: rotate(180deg);
+}
+
+.card-content {
+    padding: 15px;
+}
+
+/* 表格样式优化 */
+.table {
+    margin-bottom: 0;
+    font-size: 13px;
+}
+
+.table th {
+    background: #f8f9fa;
+    font-weight: 600;
+    padding: 10px;
+}
+
+.table td {
+    padding: 8px 10px;
+    vertical-align: middle;
+}
+
+/* 响应式布局 */
+@media (max-width: 768px) {
+    .metrics-grid {
+        grid-template-columns: 1fr;
+    }
+
+    .flex-container {
+        flex-direction: column;
+    }
+
+    .half-width {
+        width: 100%;
+    }
+}
+
+/* 滚动条样式 */
+::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
+}
+
+::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb {
+    background: #c1c1c1;
+    border-radius: 4px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+    background: #a8a8a8;
+}
+
+/* 响应式布局 */
+@media screen and (max-width: 1200px) {
+    .wrapper {
+        min-width: 600px;
+    }
+    
+    .table {
+        display: block;
+        overflow-x: auto;
+        -webkit-overflow-scrolling: touch;
+    }
+}
+
+/* 工具提示样式 */
+[title] {
+    cursor: help;
+}
+
+/* 优化建议样式 */
+#optimizationSuggestions {
+    margin: 20px 0;
+}
+
+.performance-section {
+    margin: 25px 0;
+    padding: 20px;
+    background: #fff;
+    border-radius: 10px;
+    box-shadow: 0 2px 6px rgba(0,0,0,0.08);
+}
+
+.performance-section h3 {
+    margin: 0 0 20px;
+    color: #2c3e50;
+    font-size: 18px;
+    font-weight: 600;
+    border-bottom: 2px solid #eee;
+    padding-bottom: 10px;
+}
+
+.suggestion-item {
+    margin-bottom: 20px;
+    padding: 20px;
+    border: 1px solid #e0e0e0;
+    border-radius: 8px;
+    background: #fff;
+    transition: all 0.3s ease;
+}
+
+.suggestion-item:hover {
+    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
+}
+
+.suggestion-category {
+    display: inline-block;
+    padding: 6px 12px;
+    margin-bottom: 12px;
+    border-radius: 4px;
+    font-size: 12px;
+    font-weight: 600;
+    text-transform: uppercase;
+}
+
+.category-warning {
+    background: #fff3e0;
+    color: #f57c00;
+}
+
+.category-critical {
+    background: #ffe0e0;
+    color: #d32f2f;
+}
+
+.category-info {
+    background: #e3f2fd;
+    color: #1976d2;
+}
+
+.suggestion-title {
+    margin: 10px 0;
+    font-size: 16px;
+    font-weight: 600;
+    color: #2c3e50;
+}
+
+.suggestion-description {
+    margin: 12px 0;
+    color: #5c6b7a;
+    line-height: 1.6;
+}
+
+.suggestion-list {
+    margin: 15px 0 5px;
+    padding-left: 20px;
+}
+
+.suggestion-list li {
+    margin: 8px 0;
+    color: #3c4858;
+    line-height: 1.5;
+    position: relative;
+    padding-left: 5px;
+}
+
+.suggestion-list li::before {
+    content: "•";
+    color: #666;
+    position: absolute;
+    left: -15px;
+}
+
+/* 卡片头部样式 */

+ 164 - 32
apps/page-timing/index.html

@@ -19,46 +19,178 @@
         </div>
 
         <div class="panel-body mod-json">
+            <!-- 页面基本信息 -->
+            <div id="pageInfo" class="row info-card">
+                <div class="info-item">
+                    <label>被检测的页面:</label>
+                    <span id="pageTitle">{{pageTitle}}</span>
+                </div>
+                <div class="info-item">
+                    <label>页面完整地址:</label>
+                    <span id="pageUrl">{{pageUrl}}</span>
+                </div>
+                <div class="info-item" v-if="networkInfo">
+                    <label>网络状态:</label>
+                    <span>{{networkInfo.effectiveType}} / {{networkInfo.downlink}}Mbps / RTT:{{networkInfo.rtt}}ms</span>
+                </div>
+                <div class="update-time">
+                    <span>数据更新时间:{{new Date().toLocaleString()}}</span>
+                </div>
+            </div>
+            
+            <!-- 网页优化建议 -->
+            <div class="performance-section">
+                <h3>性能优化建议</h3>
+                <div v-for="suggestion in optimizationSuggestions" :key="suggestion.title" class="suggestion-item">
+                    <span :class="['suggestion-category', 'category-' + suggestion.type]">{{suggestion.category}}</span>
+                    <h4 class="suggestion-title">{{suggestion.title}}</h4>
+                    <p class="suggestion-description">{{suggestion.description}}</p>
+                    <ul class="suggestion-list">
+                        <li v-for="item in suggestion.suggestions" :key="item">{{item}}</li>
+                    </ul>
+                </div>
+            </div>
+
+            <!-- 性能概览区域 -->
+            <div class="performance-overview">
+                <!-- 核心Web指标 -->
+                <div id="coreWebVitals" class="row performance-card" v-if="webVitals">
+                    <h3>核心Web指标 (Core Web Vitals)</h3>
+                    <div class="metrics-grid">
+                        <div class="metric-item" :class="getLCPStatus(webVitals.lcp)">
+                            <div class="metric-value">{{Math.ceil(webVitals.lcp)}} ms</div>
+                            <div class="metric-label">最大内容绘制 (LCP)</div>
+                            <div class="metric-status">{{getLCPStatusText(webVitals.lcp)}}</div>
+                        </div>
+                        <div class="metric-item" :class="getFIDStatus(webVitals.fid)">
+                            <div class="metric-value">{{webVitals.fid ? Math.ceil(webVitals.fid) + ' ms' : '未触发'}}</div>
+                            <div class="metric-label">首次输入延迟 (FID)</div>
+                            <div class="metric-status">{{getFIDStatusText(webVitals.fid)}}</div>
+                        </div>
+                        <div class="metric-item" :class="getCLSStatus(webVitals.cls)">
+                            <div class="metric-value">{{webVitals.cls ? webVitals.cls.toFixed(3) : '0'}}</div>
+                            <div class="metric-label">累积布局偏移 (CLS)</div>
+                            <div class="metric-status">{{getCLSStatusText(webVitals.cls)}}</div>
+                        </div>
+                    </div>
+                </div>
 
-            <div id="pageInfo" class="row">
-                被检测的页面:<span id="pageTitle">{{pageTitle}}</span><br>
-                页面完整地址:<span id="pageUrl">{{pageUrl}}</span>
+                <!-- 页面加载时间和内存使用 -->
+                <div class="flex-container">
+                    <div id="pageLoadTime" class="row performance-card half-width">
+                        <h3>页面加载时间</h3>
+                        <div class="timing-list" v-if="timing">
+                            <div class="timing-item" v-for="t in Object.keys(tmDefination)">
+                                <span class="timing-label">{{tmDefination[t]}}</span>
+                                <span class="timing-value">{{Math.ceil(timing[t])}} ms</span>
+                            </div>
+                        </div>
+                    </div>
+
+                    <div id="performanceMetrics" class="row performance-card half-width" v-if="performanceMetrics">
+                        <h3>内存使用情况</h3>
+                        <div class="memory-usage">
+                            <div class="memory-item">
+                                <div class="memory-label">已用/总量/限制</div>
+                                <div class="memory-value">
+                                    {{formatSize(performanceMetrics.usedJSHeapSize)}} / 
+                                    {{formatSize(performanceMetrics.totalJSHeapSize)}} / 
+                                    {{formatSize(performanceMetrics.jsHeapSizeLimit)}}
+                                </div>
+                            </div>
+                            <div class="memory-bar">
+                                <div class="used-bar" :style="{width: (performanceMetrics.usedJSHeapSize / performanceMetrics.jsHeapSizeLimit * 100) + '%'}"></div>
+                                <div class="total-bar" :style="{width: (performanceMetrics.totalJSHeapSize / performanceMetrics.jsHeapSizeLimit * 100) + '%'}"></div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            
+            <!-- HTTP Header信息 -->
+            <div id="pageHeaderInfo" class="row detail-card" v-show="headerInfo">
+                <div class="card-header" @click="toggleSection('headerInfo')">
+                    <h3>HTTP Response Header分析</h3>
+                    <span class="toggle-icon">▼</span>
+                </div>
+                <div class="card-content" v-show="sectionsVisible.headerInfo">
+                    <table class="table table-bordered table-striped table-condensed table-hover">
+                        <thead>
+                            <th>Header</th>
+                            <th>值</th>
+                        </thead>
+                        <tbody>
+                            <tr v-for="(value,key) in headerInfo">
+                                <td>{{key}}</td>
+                                <td>{{value || '-'}}</td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
             </div>
 
-            <div id="pageLoadTime" class="row">
-                <h3>页面各阶段耗时分析</h3>
-                <table class="table table-bordered table-striped table-condensed table-hover">
-                    <thead>
-                        <th >事件</th>
-                        <th >耗时</th>
-                    </thead>
-                    <tbody>
-                        <tr v-if="timing" v-for="t in Object.keys(tmDefination)">
-                            <td>{{tmDefination[t]}}</td>
-                            <td>{{Math.ceil(timing[t])}} ms</td>
-                        </tr>
-                    </tbody>
-                </table>
+            <!-- 长任务监控 -->
+            <div id="longTasks" class="row detail-card" v-if="longTasks && longTasks.length">
+                <div class="card-header" @click="toggleSection('longTasks')">
+                    <h3>长任务监控 (超过50ms的任务)</h3>
+                    <span class="toggle-icon">▼</span>
+                </div>
+                <div class="card-content" v-show="sectionsVisible.longTasks">
+                    <table class="table table-bordered table-striped table-condensed table-hover">
+                        <thead>
+                            <th>开始时间</th>
+                            <th>持续时间</th>
+                            <th>任务名称</th>
+                        </thead>
+                        <tbody>
+                            <tr v-for="task in longTasks">
+                                <td>{{formatTime(task.startTime)}}</td>
+                                <td>{{Math.ceil(task.duration)}} ms</td>
+                                <td>{{task.name}}</td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
             </div>
 
-            <div id="pageHeaderInfo" class="row" v-show="headerInfo">
-                <h3>页面HTTP Response Header分析</h3>
-                <table class="table table-bordered table-striped table-condensed table-hover">
-                    <thead>
-                        <th >网页信息</th>
-                        <th >数据</th>
-                    </thead>
-                    <tbody>
-                        <tr v-for="(value,key) in headerInfo">
-                            <td>{{key}}</td>
-                            <td>{{value || '-'}}</td>
-                        </tr>
-                    </tbody>
-                </table>
+            <!-- 详细信息区域 -->
+            <div class="details-section">
+                <!-- 资源加载性能 -->
+                <div id="resourceTiming" class="row detail-card" v-if="resources && resources.length">
+                    <div class="card-header" @click="toggleSection('resourceTiming')">
+                        <h3>资源加载性能分析</h3>
+                        <span class="toggle-icon">▼</span>
+                    </div>
+                    <div class="card-content" v-show="sectionsVisible.resourceTiming">
+                        <table class="table table-bordered table-striped table-condensed table-hover">
+                            <thead>
+                                <th>资源</th>
+                                <th>类型</th>
+                                <th>大小</th>
+                                <th>总耗时</th>
+                                <th>DNS查询</th>
+                                <th>TCP连接</th>
+                                <th>请求响应</th>
+                                <th>内容下载</th>
+                            </thead>
+                            <tbody>
+                                <tr v-for="resource in resources">
+                                    <td :title="resource.name">{{getFileName(resource.name)}}</td>
+                                    <td>{{resource.entryType}}</td>
+                                    <td>{{formatSize(resource.transferSize)}}</td>
+                                    <td>{{Math.ceil(resource.duration)}} ms</td>
+                                    <td>{{Math.ceil(resource.dnsTime)}} ms</td>
+                                    <td>{{Math.ceil(resource.tcpTime)}} ms</td>
+                                    <td>{{Math.ceil(resource.ttfb)}} ms</td>
+                                    <td>{{Math.ceil(resource.downloadTime)}} ms</td>
+                                </tr>
+                            </tbody>
+                        </table>
+                    </div>
+                </div>
 
             </div>
         </div>
-
     </div>
 
     <script type="text/javascript" src="index.js"></script>

+ 362 - 13
apps/page-timing/index.js

@@ -8,6 +8,18 @@ new Vue({
         pageUrl: '无',
         timing: null,
         headerInfo: null,
+        webVitals: null,
+        resources: null,
+        performanceMetrics: null,
+        longTasks: null,
+        networkInfo: null,
+        // 控制各个部分的显示状态
+        sectionsVisible: {
+            resourceTiming: true,
+            longTasks: true,
+            headerInfo: true,
+            optimization: true
+        },
         tmDefination: {
             lookupDomainTime: 'DNS查询耗时',
             connectTime: 'TCP连接耗时',
@@ -23,35 +35,372 @@ new Vue({
             loadTime: '加载总耗时'
         }
     },
+    computed: {
+        // 获取优化建议
+        optimizationSuggestions() {
+            const suggestions = [];
+            
+            // 性能指标建议
+            if (this.webVitals) {
+                // LCP建议
+                if (this.webVitals.lcp > 2500) {
+                    suggestions.push({
+                        category: '页面加载性能',
+                        type: 'warning',
+                        title: '最大内容绘制(LCP)需要优化',
+                        description: '当前LCP时间为 ' + Math.ceil(this.webVitals.lcp) + 'ms,超过了推荐的2500ms',
+                        suggestions: [
+                            '优化服务器响应时间,考虑使用CDN',
+                            '优化关键渲染路径,减少阻塞资源',
+                            '优化和压缩图片,考虑使用WebP格式',
+                            '实施懒加载策略',
+                            '优化CSS和JavaScript的加载顺序'
+                        ]
+                    });
+                }
+
+                // FID建议
+                if (this.webVitals.fid > 100) {
+                    suggestions.push({
+                        category: '交互响应性能',
+                        type: 'warning',
+                        title: '首次输入延迟(FID)需要改进',
+                        description: '当前FID时间为 ' + Math.ceil(this.webVitals.fid) + 'ms,超过了推荐的100ms',
+                        suggestions: [
+                            '减少主线程工作量,拆分长任务',
+                            '优化JavaScript执行时间',
+                            '延迟加载非关键JavaScript',
+                            '移除未使用的JavaScript代码',
+                            '使用Web Workers处理复杂计算'
+                        ]
+                    });
+                }
+
+                // CLS建议
+                if (this.webVitals.cls > 0.1) {
+                    suggestions.push({
+                        category: '视觉稳定性',
+                        type: 'warning',
+                        title: '累积布局偏移(CLS)需要改进',
+                        description: '当前CLS值为 ' + this.webVitals.cls.toFixed(3) + ',超过了推荐的0.1',
+                        suggestions: [
+                            '为图片和视频元素设置明确的宽高比',
+                            '避免在已存在的内容上方插入内容',
+                            '使用transform动画代替改变位置的动画',
+                            '预留足够的空间给动态加载的内容',
+                            '优化字体加载策略'
+                        ]
+                    });
+                }
+            }
+
+            // 资源优化建议
+            if (this.resources && this.resources.length) {
+                let totalSize = 0;
+                let largeResources = [];
+                let uncompressedResources = [];
+                let longLoadingResources = [];
+
+                this.resources.forEach(resource => {
+                    totalSize += resource.transferSize || 0;
+                    
+                    // 检查大文件
+                    if (resource.transferSize > 500 * 1024) { // 大于500KB
+                        largeResources.push(resource);
+                    }
+                    
+                    // 检查加载时间长的资源
+                    if (resource.duration > 1000) { // 超过1秒
+                        longLoadingResources.push(resource);
+                    }
+                });
+
+                if (totalSize > 2 * 1024 * 1024) { // 总大小超过2MB
+                    suggestions.push({
+                        category: '资源优化',
+                        type: 'warning',
+                        title: '总资源大小过大',
+                        description: '页面总资源大小为 ' + this.formatSize(totalSize),
+                        suggestions: [
+                            '使用代码分割和懒加载',
+                            '优化和压缩图片资源',
+                            '启用Gzip/Brotli压缩',
+                            '使用合适的缓存策略',
+                            '移除未使用的CSS和JavaScript代码'
+                        ]
+                    });
+                }
+
+                if (largeResources.length > 0) {
+                    suggestions.push({
+                        category: '资源优化',
+                        type: 'info',
+                        title: '发现大体积资源文件',
+                        description: '有 ' + largeResources.length + ' 个资源文件大于500KB',
+                        suggestions: largeResources.map(r => 
+                            `优化 ${this.getFileName(r.name)} (${this.formatSize(r.transferSize)})`
+                        )
+                    });
+                }
+
+                if (longLoadingResources.length > 0) {
+                    suggestions.push({
+                        category: '资源加载优化',
+                        type: 'warning',
+                        title: '资源加载时间过长',
+                        description: '有 ' + longLoadingResources.length + ' 个资源加载时间超过1秒',
+                        suggestions: longLoadingResources.map(r =>
+                            `优化 ${this.getFileName(r.name)} (${Math.ceil(r.duration)}ms)`
+                        )
+                    });
+                }
+            }
+
+            // JavaScript性能建议
+            if (this.performanceMetrics) {
+                const usedHeapRatio = this.performanceMetrics.usedJSHeapSize / this.performanceMetrics.jsHeapSizeLimit;
+                if (usedHeapRatio > 0.7) {
+                    suggestions.push({
+                        category: '内存使用优化',
+                        type: 'warning',
+                        title: 'JavaScript内存使用率过高',
+                        description: '当前内存使用率达到 ' + (usedHeapRatio * 100).toFixed(1) + '%',
+                        suggestions: [
+                            '检查内存泄漏问题',
+                            '优化大对象的创建和销毁',
+                            '使用防抖和节流控制频繁操作',
+                            '及时清理不再使用的事件监听器',
+                            '优化闭包使用,避免过度引用'
+                        ]
+                    });
+                }
+            }
+
+            // 长任务优化建议
+            if (this.longTasks && this.longTasks.length > 3) {
+                suggestions.push({
+                    category: '性能优化',
+                    type: 'warning',
+                    title: '检测到多个长任务',
+                    description: '发现 ' + this.longTasks.length + ' 个执行时间超过50ms的任务',
+                    suggestions: [
+                        '将长任务拆分为更小的任务',
+                        '使用Web Workers处理复杂计算',
+                        '优化事件处理函数',
+                        '使用requestAnimationFrame进行视觉更新',
+                        '使用requestIdleCallback处理非关键任务'
+                    ]
+                });
+            }
+
+            // 网络优化建议
+            if (this.networkInfo) {
+                if (this.networkInfo.effectiveType !== '4g') {
+                    suggestions.push({
+                        category: '网络优化',
+                        type: 'info',
+                        title: '检测到非4G网络环境',
+                        description: '当前网络类型: ' + this.networkInfo.effectiveType + ', RTT: ' + this.networkInfo.rtt + 'ms',
+                        suggestions: [
+                            '实施渐进式加载策略',
+                            '优先加载关键资源',
+                            '使用自适应加载',
+                            '考虑使用Service Worker缓存',
+                            '优化资源大小和加载顺序'
+                        ]
+                    });
+                }
+            }
+
+            // HTTP Header优化建议
+            if (this.headerInfo) {
+                const headerSuggestions = [];
+                
+                if (!this.headerInfo['cache-control']) {
+                    headerSuggestions.push('添加Cache-Control头以优化缓存策略');
+                }
+                if (!this.headerInfo['content-encoding']) {
+                    headerSuggestions.push('启用Gzip/Brotli压缩以减少传输大小');
+                }
+                if (!this.headerInfo['x-content-type-options']) {
+                    headerSuggestions.push('添加X-Content-Type-Options头以提高安全性');
+                }
+                if (!this.headerInfo['x-frame-options']) {
+                    headerSuggestions.push('添加X-Frame-Options头以防止点击劫持');
+                }
+
+                if (headerSuggestions.length > 0) {
+                    suggestions.push({
+                        category: 'HTTP优化',
+                        type: 'info',
+                        title: 'HTTP响应头优化建议',
+                        description: '发现 ' + headerSuggestions.length + ' 个HTTP头部优化建议',
+                        suggestions: headerSuggestions
+                    });
+                }
+            }
+
+            return suggestions;
+        }
+    },
     mounted: function () {
+        // 清理过期数据(7天前的数据)
+        this.cleanExpiredData();
+
         // 在tab创建或者更新时候,监听事件,看看是否有参数传递过来
         if (location.protocol === 'chrome-extension:') {
-            chrome.tabs.query({currentWindow: true,active: true, }, (tabs) => {
-                let activeTab = tabs.filter(tab => tab.active)[0];
+            chrome.tabs.query({currentWindow: true, active: true}, (tabs) => {
+                if (!tabs || !tabs.length) {
+                    console.warn('未找到活动标签页');
+                    return;
+                }
+                let activeTab = tabs[0];
                 chrome.runtime.sendMessage({
                     type: 'fh-dynamic-any-thing',
                     thing: 'request-page-content',
                     tabId: activeTab.id
                 }).then(resp => {
-                    console.log(resp)
-                    if(!resp && !resp.content) return ;
-                    sessionStorage.setItem('wpo-data', JSON.stringify(resp.content));
-                    this.showTiming(resp.content);
+                    if (!resp) {
+                        console.warn('未收到响应数据');
+                        return;
+                    }
+                    if (!resp.content) {
+                        console.warn('响应数据中没有content字段');
+                        return;
+                    }
+                    try {
+                        // 保存数据到localStorage,带上时间戳
+                        const storageData = {
+                            timestamp: Date.now(),
+                            data: resp.content
+                        };
+                        localStorage.setItem('wpo-data', JSON.stringify(storageData));
+                        this.showTiming(resp.content);
+                    } catch (e) {
+                        console.error('处理性能数据时出错:', e);
+                    }
+                }).catch(err => {
+                    console.error('获取页面性能数据失败:', err);
                 });
             });
+        } else {
+            try {
+                // 从localStorage读取数据
+                let wpoStorageData = localStorage.getItem('wpo-data');
+                if (wpoStorageData) {
+                    let storage = JSON.parse(wpoStorageData);
+                    this.showTiming(storage.data);
+                }
+            } catch (e) {
+                console.error('读取缓存的性能数据失败:', e);
+            }
         }
-
-        let wpo = JSON.parse(sessionStorage.getItem('wpo-data'));
-        wpo && this.showTiming(wpo);
     },
 
     methods: {
+        // 切换部分的显示/隐藏
+        toggleSection(section) {
+            this.sectionsVisible[section] = !this.sectionsVisible[section];
+            // 更新aria-expanded属性
+            this.$nextTick(() => {
+                const header = document.querySelector(`#${section} .card-header`);
+                if (header) {
+                    header.setAttribute('aria-expanded', this.sectionsVisible[section]);
+                }
+            });
+        },
+
+        // 清理过期数据(7天前的数据)
+        cleanExpiredData() {
+            try {
+                const wpoStorageData = localStorage.getItem('wpo-data');
+                if (wpoStorageData) {
+                    const storage = JSON.parse(wpoStorageData);
+                    const expirationTime = 7 * 24 * 60 * 60 * 1000; // 7天
+                    if (Date.now() - storage.timestamp > expirationTime) {
+                        localStorage.removeItem('wpo-data');
+                    }
+                }
+            } catch (e) {
+                console.error('清理过期数据时出错:', e);
+            }
+        },
+
         showTiming(wpo) {
-            this.pageTitle = wpo.pageInfo.title || "无";
-            this.pageUrl = wpo.pageInfo.url || "无";
+            if (!wpo || typeof wpo !== 'object') {
+                console.warn('性能数据格式不正确');
+                return;
+            }
 
-            this.timing = wpo.time;
-            this.headerInfo = wpo.header;
+            try {
+                this.pageTitle = wpo.pageInfo?.title || "无";
+                this.pageUrl = wpo.pageInfo?.url || "无";
+                this.timing = wpo.time || null;
+                this.headerInfo = wpo.header || null;
+                this.webVitals = wpo.webVitals || null;
+                this.resources = wpo.resources || null;
+                this.performanceMetrics = wpo.performanceMetrics || null;
+                this.longTasks = wpo.longTasks || null;
+                this.networkInfo = wpo.networkInfo || null;
+            } catch (e) {
+                console.error('显示性能数据时出错:', e);
+            }
+        },
+
+        // Core Web Vitals 状态判断
+        getLCPStatus(lcp) {
+            if (!lcp) return '';
+            return lcp <= 2500 ? 'text-success' : lcp <= 4000 ? 'text-warning' : 'text-danger';
+        },
+        getLCPStatusText(lcp) {
+            if (!lcp) return '未知';
+            return lcp <= 2500 ? '良好' : lcp <= 4000 ? '需要改进' : '较差';
+        },
+        getFIDStatus(fid) {
+            if (!fid) return '';
+            return fid <= 100 ? 'text-success' : fid <= 300 ? 'text-warning' : 'text-danger';
+        },
+        getFIDStatusText(fid) {
+            if (!fid) return '未触发';
+            return fid <= 100 ? '良好' : fid <= 300 ? '需要改进' : '较差';
         },
+        getCLSStatus(cls) {
+            if (!cls) return '';
+            return cls <= 0.1 ? 'text-success' : cls <= 0.25 ? 'text-warning' : 'text-danger';
+        },
+        getCLSStatusText(cls) {
+            if (!cls) return '未知';
+            return cls <= 0.1 ? '良好' : cls <= 0.25 ? '需要改进' : '较差';
+        },
+
+        // 工具函数
+        formatSize(bytes) {
+            if (!bytes) return '0 B';
+            const units = ['B', 'KB', 'MB', 'GB'];
+            let i = 0;
+            while (bytes >= 1024 && i < units.length - 1) {
+                bytes /= 1024;
+                i++;
+            }
+            return bytes.toFixed(2) + ' ' + units[i];
+        },
+
+        formatTime(timestamp) {
+            return new Date(timestamp).toLocaleTimeString('zh-CN', {
+                hour12: false,
+                hour: '2-digit',
+                minute: '2-digit',
+                second: '2-digit',
+                fractionalSecondDigits: 3
+            });
+        },
+
+        getFileName(url) {
+            try {
+                return new URL(url).pathname.split('/').pop() || url;
+            } catch (e) {
+                return url;
+            }
+        }
     }
 });

+ 0 - 123
apps/page-timing/timing.js

@@ -1,123 +0,0 @@
-/**
- * Timing.js 1.2.0
- * Copyright 2016 Addy Osmani
- * @ref https://github.com/addyosmani/timing.js/blob/master/timing.js
- */
-(function(window) {
-    'use strict';
-
-    /**
-     * 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) {
-                        // hasOwnProperty does not work because properties are
-                        // added by modifying the object prototype
-                        if(isNumeric(timing[k])) {
-                            api[k] = parseFloat(timing[k]);
-                        }
-                    }
-                }
-
-
-                // Time to first paint
-                if (api.firstPaint === undefined) {
-                    // All times are relative times to the start time within the
-                    // same objects
-                    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);
-    }
-
-    // Expose as a commonjs module
-    if (typeof module !== 'undefined' && module.exports) {
-        module.exports = window.timing;
-    }
-
-})(typeof window !== 'undefined' ? window : {});