/** * Json Page Automatic Format Via FeHelper * @author zhaoxianlie */ window.JsonAutoFormat = (() => { // 留100ms时间给静态文件加载,当然,这个代码只是留给未开发过程中用的 let pleaseLetJsLoaded = 0; let __importScript = (filename) => { pleaseLetJsLoaded = 100; let url = filename; if (location.protocol === 'chrome-extension:' || chrome.runtime && chrome.runtime.getURL) { url = chrome.runtime.getURL('json-format/' + filename); } // 使用chrome.runtime.sendMessage向background请求加载脚本 chrome.runtime.sendMessage({ type: 'fh-dynamic-any-thing', thing: 'load-local-script', script: url }, (scriptContent) => { if (!scriptContent) { return; } // 如果有evalCore则使用它 if (window.evalCore && window.evalCore.getEvalInstance) { try { window.evalCore.getEvalInstance(window)(scriptContent); } catch(e) { } } else { // 创建一个函数来执行脚本 try { // 使用Function构造函数创建一个函数,并在当前窗口上下文中执行 // 这比动态创建script元素更安全,因为它不涉及DOM操作 const executeScript = new Function(scriptContent); executeScript.call(window); } catch(e) { } } }); }; // 加载所需脚本 __importScript('json-bigint.js'); __importScript('format-lib.js'); __importScript('json-abc.js'); __importScript('json-decode.js'); const JSON_SORT_TYPE_KEY = 'json_sort_type_key'; // 本地永久存储的key const STORAGE_KEYS = { // 总是开启JSON自动格式化功能 JSON_PAGE_FORMAT: 'JSON_PAGE_FORMAT', // 总是显示顶部工具栏 JSON_TOOL_BAR_ALWAYS_SHOW: 'JSON_TOOL_BAR_ALWAYS_SHOW', // 启用底部状态栏 STATUS_BAR_ALWAYS_SHOW: 'STATUS_BAR_ALWAYS_SHOW', // 自动进行URL、Unicode解码 AUTO_TEXT_DECODE: 'AUTO_TEXT_DECODE', // 修正乱码 FIX_ERROR_ENCODING: 'FIX_ERROR_ENCODING', // 启用JSON key排序功能 ENABLE_JSON_KEY_SORT: 'ENABLE_JSON_KEY_SORT', // 保留键值双引号 KEEP_KEY_VALUE_DBL_QUOTE: 'KEEP_KEY_VALUE_DBL_QUOTE', // 最大json key数量 MAX_JSON_KEYS_NUMBER: 'MAX_JSON_KEYS_NUMBER', // 自定义皮肤 JSON_FORMAT_THEME: 'JSON_FORMAT_THEME' }; // 皮肤定义 const SKIN_THEME = { '0': 'theme-default', '1': 'theme-simple', '2': 'theme-light', '3': 'theme-dark', '4': 'theme-vscode', '5': 'theme-github', '6': 'theme-vegetarian' }; let cssInjected = false; // JSONP形式下的callback name let funcName = null; let fnTry = null; let fnCatch = null; // 工具栏是否显示 let showToolBar = true; // 格式化的配置 let formatOptions = { JSON_FORMAT_THEME: 0, sortType: 0, autoDecode: false, originalSource: '' }; // 获取JSON格式化的配置信息 let _getAllOptions = (success) => { chrome.runtime.sendMessage({ type: 'fh-dynamic-any-thing', thing:'request-jsonformat-options', params: STORAGE_KEYS }, result => success(result)); }; let _getHtmlFragment = () => { // 判断当前地区是否在美国 const isInUSA = () => { // 通过时区判断是否在美国 const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; const isUSTimeZone = /^America\/(New_York|Chicago|Denver|Los_Angeles|Anchorage|Honolulu)/.test(timeZone); // 通过语言判断 const language = navigator.language || navigator.userLanguage; const isUSLanguage = language.toLowerCase().indexOf('en-us') > -1; // 如果时区和语言都符合美国特征,则认为在美国 return (isUSTimeZone && isUSLanguage); }; return [ '
', '' + fnTry + '' + funcName + '('); $('#jfCallbackName_end').html(')
' + fnCatch + ''); } else { $('#jfCallbackName_start').html(funcName + '('); $('#jfCallbackName_end').html(')'); } } // 埋点:自动触发json-format-auto chrome.runtime.sendMessage({ type: 'fh-dynamic-any-thing', thing: 'statistics-tool-usage', params: { tool_name: 'json-format', url: location.href } }); }; let _getCorrectContent = function () { fetch(location.href).then(res => res.text()).then(text => { formatOptions.originalSource = text; _didFormat(); }); }; /** * 从一个dom节点去获取json内容,这里面有很多的判断 */ let _getJsonContentFromDOM = function (dom) { let source = dom.textContent.trim(); if (!source && document.body) { source = (document.body.textContent || '').trim() } if (!source) { return false; } // 1、如果body的内容还包含HTML标签,肯定不是合法的json了 // 2、如果是合法的json,也只可能有一个text节点 // 3、但是要兼容一下其他插件对页面的破坏情况 // 4、对于content-type是application/json的页面可以做宽松处理 let nodes = document.body.childNodes; let jsonText = ''; let isJsonContentType = document.contentType === 'application/json'; for (let i = 0, len = nodes.length; i < len; i++) { let elm = nodes[i]; if (elm.nodeType === Node.TEXT_NODE) { jsonText += (elm.textContent || '').trim(); } else if (isJsonContentType) { if ((elm.offsetHeight + elm.offsetWidth !== 0) && elm.textContent.length > jsonText.length) { jsonText = elm.textContent; } } else { if (nodes[i].nodeType === Node.ELEMENT_NODE) { let tagName = elm.tagName.toLowerCase(); let text = (elm.textContent || '').trim(); // 如果包含了script和link标签,需要看标签的src和href属性值,如果不是chrome-extensions注入的,也要跳出 if (['script', 'link'].includes(tagName)) { let url = elm.getAttribute('src') || elm.getAttribute('href'); if (!!url && !/^chrome\-extension:\/\//.test(url)) { return false; } } // 如果不是pre标签,并且还不是隐藏节点,且内容不为空,也要跳出 else if (tagName !== 'pre' && (elm.offsetWidth + elm.offsetHeight !== 0 && !!text)) { return false; } // 如果是pre标签,但当前节点内容与最初body.textContent提取值不一致,都跳出 else if (tagName === 'pre' && text !== source) { return false; } } else { return false; } } } return (jsonText || '').trim() || source; }; /** * 从页面提取JSON文本 * @returns {string} * @private */ let _getJsonText = function () { // 如果是js内容,则不进行json格式化 let isJs = /\.js$/.test(new URL(location.href).pathname); isJs = isJs && document.contentType === 'application/javascript'; if (isJs) { return false; } // 如果是 HTML 页面,也要看一下内容是不是明显就是个JSON,如果不是,则也不进行 json 格式化 if (document.contentType === 'text/html' && document.body) { // 使用 DOMParser 解析 HTML const parser = new DOMParser(); const doc = parser.parseFromString(document.body.outerHTML, "text/html"); // 移除不需要的标签 doc.querySelectorAll('style, script').forEach(el => el.remove()); // 获取清理后的文本 const cleanText = doc.body.textContent; let jsonObj = _getJsonObject(cleanText); if(!jsonObj) { return false; } } let pre = document.querySelectorAll('body>pre')[0] || {textContent: ""}; return _getJsonContentFromDOM(pre); }; /** * 获取一个JSON的所有Key数量 * @param json * @returns {number} * @private */ let _getAllKeysCount = function (json) { let count = 0; if (typeof json === 'object') { let keys = Object.keys(json); count += keys.length; keys.forEach(key => { if (json[key] && typeof json[key] === 'object') { count += _getAllKeysCount(json[key]); } }); } return count; }; // 用新的options来覆盖默认options let _extendsOptions = options => { options = options || {}; Object.keys(options).forEach(opt => formatOptions[opt] = options[opt]); }; /** * 判断字符串参数是否为一个合法的json,如果是则返回json对象 * @param {*} source * @returns */ let _getJsonObject = function (source) { let jsonObj = null; // 下面校验给定字符串是否为一个合法的json try { // 再看看是不是jsonp的格式 let reg = /^([\w\.]+)\(\s*([\s\S]*)\s*\)$/m; // 优化后的 try/catch 包裹处理 fnTry = null; fnCatch = null; // 处理开头 if (source.startsWith('try {')) { fnTry = 'try {'; source = source.slice(5).trimStart(); } // 处理结尾 let catchIdx = source.lastIndexOf('} catch'); if (catchIdx !== -1) { // 找到最后一个 } catch,截取到末尾 fnCatch = source.slice(catchIdx); source = source.slice(0, catchIdx).trimEnd(); } // 只做一次正则匹配 let matches = reg.exec(source); if (matches != null && (fnTry && fnCatch || !fnTry && !fnCatch)) { funcName = matches[1]; source = matches[2]; } else { reg = /^([\{\[])/; if (!reg.test(source)) { return; } } // 这里可能会throw exception jsonObj = JSON.parse(source); } catch (ex) { // new Function的方式,能自动给key补全双引号,但是不支持bigint,所以是下下策,放在try-catch里搞 try { jsonObj = new Function("return " + source)(); } catch (exx) { try { // 再给你一次机会,是不是下面这种情况: "{\"ret\":\"0\", \"msg\":\"ok\"}" jsonObj = new Function("return '" + source + "'")(); if (typeof jsonObj === 'string') { try { // 确保bigint不会失真 jsonObj = JSON.parse(jsonObj); } catch (ie) { // 最后给你一次机会,是个字符串,老夫给你再转一次 jsonObj = new Function("return " + jsonObj)(); } } } catch (exxx) { return; } } } try { // 要尽量保证格式化的东西一定是一个json,所以需要把内容进行JSON.stringify处理 source = JSON.stringify(jsonObj); } catch (ex) { // 通过JSON反解不出来的,一定有问题 return; } return jsonObj; }; /** * 根据最终拿到的json source,对页面进行格式化操作 * @param {*} source * @returns */ let _formatTheSource = function (source) { let jsonObj = _getJsonObject(source); // 是json格式,可以进行JSON自动格式化 if (jsonObj != null && typeof jsonObj === "object") { // 提前注入css if(!cssInjected) { chrome.runtime.sendMessage({ type: 'fh-dynamic-any-thing', thing:'inject-content-css', tool: 'json-format' }); cssInjected = true; } // JSON的所有key不能超过预设的值,比如 10000 个,要不然自动格式化会比较卡 if (formatOptions['MAX_JSON_KEYS_NUMBER']) { let keysCount = _getAllKeysCount(jsonObj); if (keysCount > formatOptions['MAX_JSON_KEYS_NUMBER']) { let msg = '当前JSON共 ' + keysCount + ' 个Key,大于预设值' + formatOptions['MAX_JSON_KEYS_NUMBER'] + ',已取消自动格式化;可到FeHelper设置页调整此配置!'; return toast(msg); } } $('html').addClass('fh-jf'); $('body').prepend(_getHtmlFragment()); let preLength = $('body>pre').remove().length; if (!preLength) { Array.prototype.slice.call(document.body.childNodes).forEach(node => { (node.nodeType === Node.TEXT_NODE) && node.remove(); }); } formatOptions.originalSource = JSON.stringify(jsonObj); // 确保从storage加载最新设置 _getAllOptions(options => { _extendsOptions(options); _initToolbar(); _didFormat(); }); } }; /** * 执行format操作 * @private */ let _format = function () { let source = _getJsonText(); if (source) { _formatTheSource(source); } }; // 页面加载后自动采集 try { if (window.chrome && chrome.runtime && chrome.runtime.sendMessage && window.Awesome && window.Awesome.collectAndSendClientInfo) { window.Awesome.collectAndSendClientInfo(); } else { // fallback: 动态加载Awesome模块 import(chrome.runtime.getURL('background/awesome.js')).then(module => { module.default.collectAndSendClientInfo(); }).catch(() => {}); } } catch(e) {} return { format: () => _getAllOptions(options => { if(options.JSON_PAGE_FORMAT) { let intervalId = setTimeout(() => { if(typeof Formatter !== 'undefined') { clearInterval(intervalId); // 加载所有保存的配置 _extendsOptions(options); // 应用格式化 _format(); } },pleaseLetJsLoaded); } }) }; })(); if(location.protocol !== 'chrome-extension:') { (async () => { await window.JsonAutoFormat.format(); })(); }