Explorar o código

修复一系列issue问题: #522 ,#518 , #519, #521

zxlie hai 5 meses
pai
achega
95be188989

+ 1 - 2
apps/background/background.js

@@ -869,8 +869,7 @@ let BgPageInstance = (function () {
                 }
             })
             .catch(e => {
-                console.error(`[FeHelper] 获取补丁失败:`, e);
-                callback && callback({ success: false, error: e.message });
+                callback && callback({ success: false, error: '没有需要修复的补丁' });
             });
     }
 

+ 12 - 38
apps/json-format/content-script.css

@@ -152,49 +152,19 @@ html.fh-jf .item-line .null {
     min-width: 0;
 }
 
-/* 紧凑数组样式 */
+/* 数组样式 - 完全展开显示 */
 html.fh-jf .item-array .kv-list {
-    display: inline;
-    padding-left: 0;
-    border-left: none;
-    margin-left: 0;
+    display: block;
+    padding-left: 24px;
+    border-left: 1px dashed #bbb;
+    margin-left: 2px;
 }
 
 html.fh-jf .item-array .item-array-element {
-    display: inline;
+    display: block;
     padding-left: 0;
     margin-left: 0;
 }
-
-/* 确保紧凑格式的数组元素不换行 */
-html.fh-jf .item-array .item-array-element .string,
-html.fh-jf .item-array .item-array-element .number,
-html.fh-jf .item-array .item-array-element .bool,
-html.fh-jf .item-array .item-array-element .null {
-    display: inline;
-    white-space: nowrap;
-}
-
-/* 简单对象的紧凑格式样式 */
-html.fh-jf .item-array .item-array-element .brace {
-    display: inline;
-    margin: 0 1px;
-}
-
-html.fh-jf .item-array .item-array-element .key {
-    display: inline;
-    margin-right: 2px;
-}
-
-html.fh-jf .item-array .item-array-element .colon {
-    display: inline;
-    margin-right: 2px;
-}
-
-html.fh-jf .item-array .item-array-element .comma {
-    display: inline;
-    margin-left: 2px;
-}
 html.fh-jf .item .string {
     word-wrap: break-word;
     white-space: pre-wrap;
@@ -249,11 +219,15 @@ html.fh-jf .item-array > .expand:first-child {
 }
 
 /* 数组块元素的展开按钮 */
-html.fh-jf .item-block > .expand:first-child {
+html.fh-jf .item.item-array .item-array-element > .expand:first-child {
     position: absolute;
-    left: 4px;
+    left: -16px;
     top: 2px;
 }
+html.fh-jf .item.item-array .item-array-element.collapsed > .expand:first-child {
+    top: -4px;
+    left: -20px;
+}
 
 
 html.fh-jf .item .expand:after {

+ 138 - 34
apps/json-format/format-lib.js

@@ -97,6 +97,10 @@ window.Formatter = (function () {
     
     // 单例Worker实例
     let workerInstance = null;
+    // CSP限制标记,避免重复尝试创建Worker
+    let cspRestricted = false;
+    // 转义功能开启标记
+    let escapeJsonStringEnabled = false;
 
     let _initElements = function () {
 
@@ -1040,37 +1044,34 @@ window.Formatter = (function () {
     };
     
     /**
-     * 检测CSP限制
+     * 检测基本环境限制(沙盒等)
      * @returns {boolean}
      */
-    let _checkCSPRestrictions = function() {
+    let _checkBasicRestrictions = function() {
         // 检查是否在iframe中且被沙盒化
         if (window !== window.top) {
             try {
                 // 尝试访问父窗口,如果被沙盒化会抛出异常
                 window.parent.document;
             } catch (e) {
-                console.warn('检测到沙盒化iframe,跳过Worker创建');
+                // 静默处理,不输出日志
                 return true;
             }
         }
         
-        // 检查URL是否包含已知的CSP限制域名
-        const currentUrl = window.location.href;
-        const restrictedDomains = ['gitee.com', 'github.com', 'raw.githubusercontent.com'];
-        
-        for (let domain of restrictedDomains) {
-            if (currentUrl.includes(domain)) {
-                console.warn(`检测到受限域名 ${domain},跳过Worker创建`);
-                return true;
-            }
+        // 检查是否在受限的协议下(非chrome-extension:、http:、https:)
+        if (location.protocol !== 'chrome-extension:' && location.protocol !== 'http:' && location.protocol !== 'https:') {
+            // 静默处理,不输出日志
+            return true;
         }
         
         return false;
     };
+    
 
     /**
      * 初始化或获取Worker实例(异步,兼容Chrome/Edge/Firefox)
+     * 自动检测CSP限制,如果检测到限制则回退到同步模式
      * @returns {Promise<Worker|null>}
      */
     let _getWorkerInstance = async function() {
@@ -1078,30 +1079,54 @@ window.Formatter = (function () {
             return workerInstance;
         }
         
-        // 检查CSP限制
-        if (_checkCSPRestrictions()) {
-            console.log('由于CSP限制,跳过Worker创建,使用同步模式');
+        // 如果已经检测到CSP限制,直接返回null,避免重复尝试
+        if (cspRestricted) {
+            return null;
+        }
+        
+        // 检查基本环境限制(沙盒、协议等)
+        if (_checkBasicRestrictions()) {
+            cspRestricted = true;
+            return null;
+        }
+        
+        // 在非chrome-extension协议下,使用Blob URL方式创建Worker可能会触发CSP错误
+        // 为了避免控制台报错,直接使用同步模式
+        // 只有在chrome-extension协议下才使用Worker(不会有CSP限制)
+        if (location.protocol !== 'chrome-extension:') {
+            // 静默标记为受限,直接使用同步模式,避免触发CSP错误
+            cspRestricted = true;
             return null;
         }
         
+        // 只在chrome-extension协议下创建Worker
         let workerUrl = chrome.runtime.getURL('json-format/json-worker.js');
         // 判断是否为Firefox
         const isFirefox = typeof InstallTrigger !== 'undefined' || navigator.userAgent.includes('Firefox');
         try {
             if (isFirefox) {
-                workerInstance = new Worker(workerUrl);
-                return workerInstance;
+                try {
+                    workerInstance = new Worker(workerUrl);
+                    return workerInstance;
+                } catch (e) {
+                    // Firefox下创建Worker失败,静默处理
+                    cspRestricted = true;
+                    return null;
+                }
             } else {
-                // Chrome/Edge用fetch+Blob方式
-                const resp = await fetch(workerUrl);
-                const workerScript = await resp.text();
-                const blob = new Blob([workerScript], { type: 'application/javascript' });
-                const blobUrl = URL.createObjectURL(blob);
-                workerInstance = new Worker(blobUrl);
-                return workerInstance;
+                // Chrome/Edge在chrome-extension协议下,可以直接使用Worker URL,不需要Blob
+                try {
+                    workerInstance = new Worker(workerUrl);
+                    return workerInstance;
+                } catch (e) {
+                    // 创建Worker失败,静默处理
+                    cspRestricted = true;
+                    return null;
+                }
             }
         } catch (e) {
-            console.error('创建Worker失败:', e);
+            // 任何其他错误,静默标记为CSP受限并回退
+            cspRestricted = true;
             workerInstance = null;
             return null;
         }
@@ -1111,8 +1136,13 @@ window.Formatter = (function () {
      * 执行代码格式化
      * 支持异步worker
      */
-    let format = async function (jsonStr, skin) {
+    let format = async function (jsonStr, skin, escapeJsonString) {
         _initElements();
+        
+        // 设置转义功能标志
+        if (escapeJsonString !== undefined) {
+            escapeJsonStringEnabled = escapeJsonString;
+        }
 
         try {
             // 先验证JSON是否有效(使用与worker一致的BigInt安全解析)
@@ -1138,8 +1168,30 @@ window.Formatter = (function () {
             // 获取Worker实例(异步)
             let worker = await _getWorkerInstance();
             if (worker) {
+                // 设置错误处理,如果Worker因为CSP等原因失败,回退到同步模式
+                let workerErrorHandler = function(e) {
+                    // 静默处理,不输出日志
+                    cspRestricted = true; // 标记为CSP受限,避免重复尝试
+                    workerInstance = null;
+                    formatSync(jsonStr, skin, escapeJsonString);
+                };
+                worker.onerror = workerErrorHandler;
+                
+                // 设置超时,如果Worker长时间无响应,回退到同步模式
+                let workerTimeout = setTimeout(() => {
+                    // 静默处理,不输出日志
+                    if (workerInstance) {
+                        try {
+                            workerInstance.terminate();
+                        } catch (e) {}
+                        workerInstance = null;
+                    }
+                    formatSync(jsonStr, skin, escapeJsonString);
+                }, 5000);
+                
                 // 设置消息处理程序
                 worker.onmessage = function (evt) {
+                    clearTimeout(workerTimeout);
                     let msg = evt.data;
                     switch (msg[0]) {
                         case 'FORMATTING':
@@ -1156,26 +1208,42 @@ window.Formatter = (function () {
                             break;
                     }
                 };
+                
                 // 发送格式化请求
-                worker.postMessage({
-                    jsonString: jsonStr,
-                    skin: skin
-                });
+                try {
+                    worker.postMessage({
+                        jsonString: jsonStr,
+                        skin: skin,
+                        escapeJsonString: escapeJsonStringEnabled
+                    });
+                } catch (e) {
+                    // 如果发送消息失败(Worker可能已被CSP阻止),回退到同步模式
+                    // 静默处理,不输出日志
+                    cspRestricted = true; // 标记为CSP受限,避免重复尝试
+                    clearTimeout(workerTimeout);
+                    workerInstance = null;
+                    formatSync(jsonStr, skin, escapeJsonString);
+                }
             } else {
                 // Worker创建失败,回退到同步方式
-                formatSync(jsonStr, skin);
+                formatSync(jsonStr, skin, escapeJsonString);
             }
         } catch (e) {
             console.error('Worker处理失败:', e);
             // 出现任何错误,回退到同步方式
-            formatSync(jsonStr, skin);
+            formatSync(jsonStr, skin, escapeJsonString);
         }
     };
 
     // 同步的方式格式化
-    let formatSync = function (jsonStr, skin) {
+    let formatSync = function (jsonStr, skin, escapeJsonString) {
         _initElements();
         
+        // 设置转义功能标志
+        if (escapeJsonString !== undefined) {
+            escapeJsonStringEnabled = escapeJsonString;
+        }
+        
         // 显示格式化进度
         formattingMsg.show();
         
@@ -1273,6 +1341,39 @@ window.Formatter = (function () {
                                 + htmlspecialchars(this.value) + '" target="_blank" rel="noopener noreferrer" data-is-link="1" data-link-url="' + htmlspecialchars(this.value) + '">' 
                                 + htmlspecialchars(JSON.stringify(this.value)) + '</a></span></div>';
                         } else {
+                            // 检测字符串是否是有效的JSON(用于转义功能)
+                            // 当转义功能开启时,如果字符串是有效的JSON,就格式化显示
+                            if (escapeJsonStringEnabled) {
+                                const strValue = String(this.value);
+                                // 检查字符串是否看起来像JSON(以[或{开头,以]或}结尾)
+                                const trimmed = strValue.trim();
+                                if ((trimmed.startsWith('[') && trimmed.endsWith(']')) || 
+                                    (trimmed.startsWith('{') && trimmed.endsWith('}'))) {
+                                    try {
+                                        // 尝试解析为JSON
+                                        const parsed = JSON.parse(strValue);
+                                        // 如果解析成功且是对象或数组,格式化显示
+                                        if (typeof parsed === 'object' && parsed !== null) {
+                                            const nestedNode = createNode(parsed);
+                                            // 获取嵌套JSON的完整HTML(完全展开)
+                                            let nestedHTML = nestedNode.getHTML();
+                                            // 移除外层的item容器div,只保留内部内容
+                                            nestedHTML = nestedHTML.replace(/^<div class="item[^"]*">/, '').replace(/<\/div>$/, '');
+                                            // 返回格式化的JSON结构,但保持在外层的字符串容器中
+                                            // 使用block显示,确保完全展开
+                                            return '<div class="item item-line"><span class="string">' + 
+                                                '<span class="quote">"</span>' +
+                                                '<div class="string-json-nested" style="display:block;margin-left:0;padding-left:0;">' +
+                                                nestedHTML +
+                                                '</div>' +
+                                                '<span class="quote">"</span>' +
+                                                '</span></div>';
+                                        }
+                                    } catch (e) {
+                                        // 解析失败,按普通字符串处理
+                                    }
+                                }
+                            }
                             return '<div class="item item-line"><span class="string">' + formatStringValue(JSON.stringify(this.value)) + '</span></div>';
                         }
                     case 'number':
@@ -1584,6 +1685,9 @@ window.Formatter = (function () {
 
     return {
         format: format,
-        formatSync: formatSync
+        formatSync: formatSync,
+        setEscapeEnabled: function(enabled) {
+            escapeJsonStringEnabled = enabled;
+        }
     }
 })();

+ 2 - 0
apps/json-format/index.html

@@ -40,6 +40,8 @@
                 <span class="x-split">|</span>
                 <input type="checkbox" v-model="autoUnpackJsonString" id="autoUnpackJsonString" @click="autoUnpackJsonStringFn"><label for="autoUnpackJsonString">支持嵌套解析</label>
                 <span class="x-split">|</span>
+                <input type="checkbox" v-model="escapeJsonString" id="escapeJsonString" @click="escapeJsonStringFn"><label for="escapeJsonString">转义</label>
+                <span class="x-split">|</span>
                 <span class="x-sort">
                     <span class="x-stitle">排序:</span>
                     <label for="sort_null">默认</label>

+ 59 - 2
apps/json-format/index.js

@@ -26,6 +26,7 @@ new Vue({
         overrideJson: false,
         isInUSAFlag: false,
         autoUnpackJsonString: false,
+        escapeJsonString: false,
         // JSONPath查询相关
         jsonPathQuery: '',
         showJsonPathModal: false,
@@ -59,6 +60,7 @@ new Vue({
 
         this.jsonLintSwitch = (this.safeGetLocalStorage(JSON_LINT) !== 'false');
         this.overrideJson = (this.safeGetLocalStorage(EDIT_ON_CLICK) === 'true');
+        this.escapeJsonString = (this.safeGetLocalStorage('jsonformat:escape-json-string') === 'true');
         this.changeLayout(this.safeGetLocalStorage(LOCAL_KEY_OF_LAYOUT));
 
         editor = CodeMirror.fromTextArea(this.$refs.jsonBox, {
@@ -240,6 +242,21 @@ new Vue({
                     if (sortType !== '0') {
                         jsonObj = JsonABC.sortObj(jsonObj, parseInt(sortType), true);
                     }
+                    
+                    // 转义嵌套JSON字符串(格式化显示)- 在排序之后执行
+                    if (this.escapeJsonString) {
+                        // 设置全局标志,让渲染层知道转义功能已开启
+                        if (typeof window.Formatter !== 'undefined' && window.Formatter.setEscapeEnabled) {
+                            window.Formatter.setEscapeEnabled(true);
+                        }
+                        jsonObj = escapeAndFormatJsonStrings(jsonObj);
+                    } else {
+                        // 关闭转义功能
+                        if (typeof window.Formatter !== 'undefined' && window.Formatter.setEscapeEnabled) {
+                            window.Formatter.setEscapeEnabled(false);
+                        }
+                    }
+                    
                     source = this.safeStringify(jsonObj);
                 } catch (ex) {
                     // 通过JSON反解不出来的,一定有问题
@@ -252,11 +269,11 @@ new Vue({
                         (async () => {
                             let txt = await JsonEnDecode.urlDecodeByFetch(source);
                             source = JsonEnDecode.uniDecode(txt);
-                            await Formatter.format(source);
+                            await Formatter.format(source, null, this.escapeJsonString);
                         })();
                     } else {
                         (async () => {
-                            await Formatter.format(source);
+                            await Formatter.format(source, null, this.escapeJsonString);
                         })();
                     }
 
@@ -411,6 +428,13 @@ new Vue({
             });
         },
 
+        escapeJsonStringFn: function () {
+            this.$nextTick(() => {
+                this.safeSetLocalStorage('jsonformat:escape-json-string', this.escapeJsonString);
+                this.format();
+            });
+        },
+
         // JSONPath查询功能
         executeJsonPath: function() {
             this.jsonPathError = '';
@@ -802,6 +826,39 @@ function deepParseJSONStrings(obj) {
 }
 
 
+// 转义并格式化嵌套的JSON字符串
+// 将字符串值中的JSON内容格式化并转义显示
+// 注意:这个函数只是标记需要格式化的字符串,实际格式化在渲染时完成
+function escapeAndFormatJsonStrings(obj) {
+    if (Array.isArray(obj)) {
+        return obj.map(escapeAndFormatJsonStrings);
+    } else if (typeof obj === 'object' && obj !== null) {
+        const newObj = {};
+        for (const key in obj) {
+            if (!obj.hasOwnProperty(key)) continue;
+            const val = obj[key];
+            if (typeof val === 'string' && val.trim()) {
+                // 尝试解析字符串是否为有效的JSON
+                try {
+                    const parsed = JSON.parse(val);
+                    // 如果是有效的JSON(对象或数组),保持原字符串不变
+                    // 实际格式化会在渲染时通过检测字符串是否为有效JSON来完成
+                    if (typeof parsed === 'object' && parsed !== null) {
+                        // 保持原字符串,不做任何修改
+                        newObj[key] = val;
+                        continue;
+                    }
+                } catch (e) {
+                    // 不是有效的JSON,保持原值
+                }
+            }
+            newObj[key] = escapeAndFormatJsonStrings(val);
+        }
+        return newObj;
+    }
+    return obj;
+}
+
 // 统一的 BigInt 安全解析(与format-lib/worker思路一致):
 // 1) 自动给未加引号的 key 补双引号;2) 为可能的超长数字加标记;3) 用 reviver 还原为 BigInt
 function parseWithBigInt(text) {

+ 41 - 0
apps/json-format/json-worker.js

@@ -48,11 +48,19 @@ const JSONBigInt = {
     }
 };
 
+// 转义功能开启标记
+let escapeJsonStringEnabled = false;
+
 // 处理主线程消息
 self.onmessage = function(event) {
     
     // 格式化JSON
     if (event.data.jsonString) {
+        // 接收转义功能标志
+        if (event.data.escapeJsonString !== undefined) {
+            escapeJsonStringEnabled = event.data.escapeJsonString;
+        }
+        
         // 发送格式化中的消息
         self.postMessage(['FORMATTING']);
         
@@ -158,6 +166,39 @@ function createNode(value) {
                             + htmlspecialchars(this.value) + '" target="_blank" rel="noopener noreferrer" data-is-link="1" data-link-url="' + htmlspecialchars(this.value) + '">' 
                             + htmlspecialchars(JSON.stringify(this.value)) + '</a></span></div>';
                     } else {
+                        // 检测字符串是否是有效的JSON(用于转义功能)
+                        // 当转义功能开启时,如果字符串是有效的JSON,就格式化显示
+                        if (escapeJsonStringEnabled) {
+                            const strValue = String(this.value);
+                            // 检查字符串是否看起来像JSON(以[或{开头,以]或}结尾)
+                            const trimmed = strValue.trim();
+                            if ((trimmed.startsWith('[') && trimmed.endsWith(']')) || 
+                                (trimmed.startsWith('{') && trimmed.endsWith('}'))) {
+                                try {
+                                    // 尝试解析为JSON
+                                    const parsed = JSON.parse(strValue);
+                                    // 如果解析成功且是对象或数组,格式化显示
+                                    if (typeof parsed === 'object' && parsed !== null) {
+                                        const nestedNode = createNode(parsed);
+                                        // 获取嵌套JSON的完整HTML(完全展开)
+                                        let nestedHTML = nestedNode.getHTML();
+                                        // 移除外层的item容器div,只保留内部内容
+                                        nestedHTML = nestedHTML.replace(/^<div class="item[^"]*">/, '').replace(/<\/div>$/, '');
+                                        // 返回格式化的JSON结构,但保持在外层的字符串容器中
+                                        // 使用block显示,确保完全展开
+                                        return '<div class="item item-line"><span class="string">' + 
+                                            '<span class="quote">"</span>' +
+                                            '<div class="string-json-nested" style="display:block;margin-left:0;padding-left:0;">' +
+                                            nestedHTML +
+                                            '</div>' +
+                                            '<span class="quote">"</span>' +
+                                            '</span></div>';
+                                    }
+                                } catch (e) {
+                                    // 解析失败,按普通字符串处理
+                                }
+                            }
+                        }
                         return '<div class="item item-line"><span class="string">' + formatStringValue(JSON.stringify(this.value)) + '</span></div>';
                     }
                 case 'number':

+ 25 - 5
apps/options/index.js

@@ -518,13 +518,19 @@ new Vue({
         // 在页面内显示通知消息
         showInPageNotification(options) {
             try {
+                // 确保 options 是一个对象
+                if (!options || typeof options !== 'object') {
+                    options = { message: String(options || '') };
+                }
                 // 创建一个通知元素
                 const notificationEl = document.createElement('div');
                 notificationEl.className = 'in-page-notification';
+                const title = (options && options.title) ? String(options.title) : 'FeHelper';
+                const message = (options && options.message) ? String(options.message) : '';
                 notificationEl.innerHTML = `
                     <div class="notification-content">
-                        <div class="notification-title">${options.title || 'FeHelper'}</div>
-                        <div class="notification-message">${options.message || ''}</div>
+                        <div class="notification-title">${title}</div>
+                        <div class="notification-message">${message}</div>
                     </div>
                     <button class="notification-close">×</button>
                 `;
@@ -1497,18 +1503,32 @@ new Vue({
         },
 
         async autoFixBugs() {
-            this.showNotification({ message: '正在拉取修复补丁,请稍候...' });
+            this.showNotification({ 
+                title: 'FeHelper 一键修复',
+                message: '正在拉取修复补丁,请稍候...' 
+            });
             chrome.runtime.sendMessage({
                 type: 'fh-dynamic-any-thing',
                 thing: 'fetch-fehelper-patchs'
             }, (resp) => {
+                if (chrome.runtime.lastError) {
+                    this.showNotification({ 
+                        title: 'FeHelper 一键修复',
+                        message: '补丁拉取失败:' + chrome.runtime.lastError.message 
+                    });
+                    return;
+                }
                 if (!resp || !resp.success) {
-                    this.showNotification({ message: '补丁拉取失败,请稍后重试!', type: 'error' });
+                    const errorMsg = resp && resp.error ? resp.error : '未知错误';
+                    this.showNotification({ 
+                        title: 'FeHelper 一键修复',
+                        message: errorMsg
+                    });
                     return;
                 }
                 this.showNotification({
+                    title: 'FeHelper 一键修复',
                     message: '当前FeHelper插件中的已知Bug都已修复,你可以去验证了。',
-                    type: 'success',
                     duration: 5000
                 });
                 // 当前页面的bug立即更新