content-script.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. /**
  2. * Json Page Automatic Format Via FeHelper
  3. * @author zhaoxianlie
  4. */
  5. // 留100ms时间给静态文件加载,当然,这个代码只是留给未开发过程中用的
  6. let pleaseLetJsLoaded = 0;
  7. let __importScript = (filename) => {
  8. pleaseLetJsLoaded = 100;
  9. let url = filename;
  10. if (location.protocol === 'chrome-extension:' || chrome.runtime && chrome.runtime.getURL) {
  11. url = chrome.runtime.getURL('json-format/' + filename);
  12. }
  13. fetch(url).then(resp => resp.text()).then(jsText => {
  14. if(window.evalCore && window.evalCore.getEvalInstance){
  15. return window.evalCore.getEvalInstance(window)(jsText);
  16. }
  17. let el = document.createElement('script');
  18. el.textContent = jsText;
  19. document.head.appendChild(el);
  20. });
  21. };
  22. __importScript('json-bigint.js');
  23. __importScript('format-lib.js');
  24. __importScript('json-abc.js');
  25. __importScript('json-decode.js');
  26. window.JsonAutoFormat = (() => {
  27. "use strict";
  28. const JSON_SORT_TYPE_KEY = 'json_sort_type_key';
  29. const JSON_AUTO_DECODE = 'json_auto_decode';
  30. const JSON_TOOL_BAR_ALWAYS_SHOW = 'JSON_TOOL_BAR_ALWAYS_SHOW';
  31. // 用于记录最原始的json串
  32. let originalJsonStr = '';
  33. let curSortType = 0;
  34. // JSONP形式下的callback name
  35. let funcName = null;
  36. let jsonObj = null;
  37. let fnTry = null;
  38. let fnCatch = null;
  39. let autoDecode = false;
  40. let _getHtmlFragment = () => {
  41. return [
  42. '<div class="x-toolbar" style="display:none">' +
  43. ' <a href="http://www.baidufe.com/fehelper/feedback.html" target="_blank" class="x-a-title">' +
  44. ' <img src="' + chrome.runtime.getURL('static/img/fe-16.png') + '" alt="fehelper"/> FeHelper</a>' +
  45. ' <span class="x-b-title"></span>' +
  46. ' <span class="x-split">|</span>\n' +
  47. ' <input type="checkbox" id="json_endecode"><label for="json_endecode">自动解码</label>' +
  48. ' <span class="x-sort">' +
  49. ' <span class="x-split">|</span>' +
  50. ' <span class="x-stitle">排序:</span>' +
  51. ' <label for="sort_null">默认</label><input type="radio" name="jsonsort" id="sort_null" value="0" checked>' +
  52. ' <label for="sort_asc">升序</label><input type="radio" name="jsonsort" id="sort_asc" value="1">' +
  53. ' <label for="sort_desc">降序</label><input type="radio" name="jsonsort" id="sort_desc" value="-1">' +
  54. ' </span>' +
  55. ' <span class="x-split">|</span>\n' +
  56. ' <button class="xjf-btn" id="jsonGetCorrectCnt">乱码修正</button>' +
  57. ' <span id="optionBar"></span>' +
  58. ' <span class="fe-feedback">' +
  59. ' <a id="toggleBtn" title="展开或收起工具栏">隐藏&gt;&gt;</a>' +
  60. ' </span>' +
  61. '</div>',
  62. '<div id="formattingMsg"><span class="x-loading"></span>格式化中...</div>',
  63. '<div class="mod-json mod-contentscript"><div class="rst-item">',
  64. '<div id="jfCallbackName_start" class="callback-name"></div>',
  65. '<div id="jfContent"></div>',
  66. '<pre id="jfContent_pre"></pre>',
  67. '<div id="jfCallbackName_end" class="callback-name"></div>',
  68. '</div></div>'
  69. ].join('')
  70. };
  71. /**
  72. * 从页面提取JSON文本
  73. * @returns {string}
  74. * @private
  75. */
  76. let _getJsonText = function () {
  77. let pre = document.querySelectorAll('body>pre')[0] || {textContent: ""};
  78. let source = pre.textContent.trim();
  79. if (!source) {
  80. source = (document.body.textContent || '').trim()
  81. }
  82. if (!source) {
  83. return false;
  84. }
  85. // 1、如果body的内容还包含HTML标签,肯定不是合法的json了
  86. // 2、如果是合法的json,也只可能有一个text节点
  87. // 3、但是要兼容一下其他插件对页面的破坏情况
  88. // 4、对于content-type是application/json的页面可以做宽松处理
  89. let nodes = document.body.childNodes;
  90. let jsonText = '';
  91. let isJsonContentType = document.contentType === 'application/json';
  92. for (let i = 0, len = nodes.length; i < len; i++) {
  93. let elm = nodes[i];
  94. if (elm.nodeType === Node.TEXT_NODE) {
  95. jsonText += (elm.textContent || '').trim();
  96. } else if (isJsonContentType) {
  97. if ((elm.offsetHeight + elm.offsetWidth !== 0) && elm.textContent.length > jsonText.length) {
  98. jsonText = elm.textContent;
  99. }
  100. } else {
  101. if (nodes[i].nodeType === Node.ELEMENT_NODE) {
  102. let tagName = elm.tagName.toLowerCase();
  103. let text = (elm.textContent || '').trim();
  104. // 如果是pre标签,则看内容是不是和source一样,一样则continue
  105. if (!((tagName === 'pre' && text === source)
  106. || ((elm.offsetWidth + elm.offsetHeight === 0 || !text)
  107. && !['script', 'link'].includes(tagName)))) {
  108. return false;
  109. }
  110. } else {
  111. return false;
  112. }
  113. }
  114. }
  115. return (jsonText || '').trim() || source;
  116. };
  117. /**
  118. * 获取一个JSON的所有Key数量
  119. * @param json
  120. * @returns {number}
  121. * @private
  122. */
  123. let _getAllKeysCount = function (json) {
  124. let count = 0;
  125. if (typeof json === 'object') {
  126. let keys = Object.keys(json);
  127. count += keys.length;
  128. keys.forEach(key => {
  129. if (json[key] && typeof json[key] === 'object') {
  130. count += _getAllKeysCount(json[key]);
  131. }
  132. });
  133. }
  134. return count;
  135. };
  136. /**
  137. * 执行format操作
  138. * @private
  139. */
  140. let _format = function (options) {
  141. let source = _getJsonText();
  142. if (!source) {
  143. return;
  144. }
  145. // 下面校验给定字符串是否为一个合法的json
  146. try {
  147. // 再看看是不是jsonp的格式
  148. let reg = /^([\w\.]+)\(\s*([\s\S]*)\s*\)$/gm;
  149. let reTry = /^(try\s*\{\s*)?/g;
  150. let reCatch = /([;\s]*\}\s*catch\s*\(\s*\S+\s*\)\s*\{([\s\S])*\})?[;\s]*$/g;
  151. // 检测是否有try-catch包裹
  152. let sourceReplaced = source.replace(reTry, function () {
  153. fnTry = fnTry ? fnTry : arguments[1];
  154. return '';
  155. }).replace(reCatch, function () {
  156. fnCatch = fnCatch ? fnCatch : arguments[1];
  157. return '';
  158. }).trim();
  159. let matches = reg.exec(sourceReplaced);
  160. if (matches != null && (fnTry && fnCatch || !fnTry && !fnCatch)) {
  161. funcName = matches[1];
  162. source = matches[2];
  163. } else {
  164. reg = /^([\{\[])/;
  165. if (!reg.test(source)) {
  166. return;
  167. }
  168. }
  169. // 这里可能会throw exception
  170. jsonObj = JSON.parse(source);
  171. } catch (ex) {
  172. // new Function的方式,能自动给key补全双引号,但是不支持bigint,所以是下下策,放在try-catch里搞
  173. try {
  174. jsonObj = new Function("return " + source)();
  175. } catch (exx) {
  176. try {
  177. // 再给你一次机会,是不是下面这种情况: "{\"ret\":\"0\", \"msg\":\"ok\"}"
  178. jsonObj = new Function("return '" + source + "'")();
  179. if (typeof jsonObj === 'string') {
  180. try {
  181. // 确保bigint不会失真
  182. jsonObj = JSON.parse(jsonObj);
  183. } catch (ie) {
  184. // 最后给你一次机会,是个字符串,老夫给你再转一次
  185. jsonObj = new Function("return " + jsonObj)();
  186. }
  187. }
  188. } catch (exxx) {
  189. return;
  190. }
  191. }
  192. }
  193. // 是json格式,可以进行JSON自动格式化
  194. if (jsonObj != null && typeof jsonObj === "object") {
  195. try {
  196. // 要尽量保证格式化的东西一定是一个json,所以需要把内容进行JSON.stringify处理
  197. source = JSON.stringify(jsonObj);
  198. } catch (ex) {
  199. // 通过JSON反解不出来的,一定有问题
  200. return;
  201. }
  202. // JSON的所有key不能超过预设的值,比如 10000 个,要不然自动格式化会比较卡
  203. if (options && options['MAX_JSON_KEYS_NUMBER']) {
  204. let keysCount = _getAllKeysCount(jsonObj);
  205. if (keysCount > options['MAX_JSON_KEYS_NUMBER']) {
  206. let msg = '当前JSON共 <b style="color:red">' + keysCount + '</b> 个Key,大于预设值' + options['MAX_JSON_KEYS_NUMBER'] + ',已取消自动格式化;可到FeHelper设置页调整此配置!';
  207. return toast(msg);
  208. }
  209. }
  210. if (window.jsonformatContentScriptCssInject) {
  211. window.jsonformatContentScriptCssInject();
  212. } else {
  213. // 注入css and html fragment
  214. chrome.runtime.sendMessage({
  215. type: 'fh-dynamic-any-thing'
  216. },(params) => {
  217. let injectFn = (cssText) => {
  218. chrome.tabs.insertCSS({
  219. code: cssText
  220. });
  221. };
  222. let cssText = Awesome.getContentScript('json-format', true);
  223. if (typeof cssText === 'string' && cssText.length) {
  224. injectFn(cssText);
  225. } else if (cssText instanceof Promise) {
  226. cssText.then(css => {
  227. if (css) {
  228. injectFn(css)
  229. } else {
  230. fetch('../json-format/content-script.css').then(resp => resp.text()).then(css => injectFn(css));
  231. }
  232. });
  233. } else if (!cssText) {
  234. fetch('../json-format/content-script.css').then(resp => resp.text()).then(css => injectFn(css));
  235. }
  236. return true;
  237. });
  238. }
  239. let preLength = $('body>pre').hide().length;
  240. $('body').prepend(_getHtmlFragment());
  241. if (!preLength) {
  242. Array.prototype.slice.call(document.body.childNodes).forEach(node => {
  243. (node.nodeType === Node.TEXT_NODE) && node.remove();
  244. });
  245. }
  246. originalJsonStr = source;
  247. // 获取上次记录的排序方式
  248. curSortType = parseInt(localStorage.getItem(JSON_SORT_TYPE_KEY) || 0);
  249. _didFormat(curSortType);
  250. // 排序选项初始化
  251. $('[name=jsonsort][value=' + curSortType + ']').attr('checked', 1);
  252. // 自动解码选项初始化
  253. autoDecode = localStorage.getItem(JSON_AUTO_DECODE);
  254. if (autoDecode === null) {
  255. autoDecode = (options && options['AUTO_TEXT_DECODE']);
  256. } else {
  257. autoDecode = autoDecode === 'true';
  258. }
  259. $('#json_endecode').prop('checked', autoDecode);
  260. _bindEvent();
  261. }
  262. };
  263. let _didFormat = function (sortType) {
  264. sortType = sortType || 0;
  265. let source = originalJsonStr;
  266. if (sortType !== 0) {
  267. let jsonObj = JsonABC.sortObj(JSON.parse(originalJsonStr), parseInt(sortType), true);
  268. source = JSON.stringify(jsonObj);
  269. }
  270. if (autoDecode) {
  271. (async () => {
  272. let txt = await JsonEnDecode.urlDecodeByFetch(source);
  273. source = JsonEnDecode.uniDecode(txt);
  274. // 格式化
  275. Formatter.format(source);
  276. $('.x-toolbar').fadeIn(500);
  277. })();
  278. } else {
  279. // 格式化
  280. Formatter.format(source);
  281. $('.x-toolbar').fadeIn(500);
  282. }
  283. // 如果是JSONP格式的,需要把方法名也显示出来
  284. if (funcName != null) {
  285. if (fnTry && fnCatch) {
  286. $('#jfCallbackName_start').html('<pre style="padding:0">' + fnTry + '</pre>' + funcName + '(');
  287. $('#jfCallbackName_end').html(')<br><pre style="padding:0">' + fnCatch + '</pre>');
  288. } else {
  289. $('#jfCallbackName_start').html(funcName + '(');
  290. $('#jfCallbackName_end').html(')');
  291. }
  292. }
  293. };
  294. let _getCorrectContent = function () {
  295. fetch(location.href).then(res => res.text()).then(text => {
  296. originalJsonStr = text;
  297. _didFormat(curSortType);
  298. });
  299. };
  300. let _bindEvent = function () {
  301. $('[name=jsonsort]').click(function (e) {
  302. let sortType = parseInt(this.value);
  303. if (sortType !== curSortType) {
  304. _didFormat(sortType);
  305. curSortType = sortType;
  306. }
  307. localStorage.setItem(JSON_SORT_TYPE_KEY, sortType);
  308. });
  309. $('#json_endecode').click(function (e) {
  310. autoDecode = this.checked;
  311. localStorage.setItem(JSON_AUTO_DECODE, autoDecode);
  312. _didFormat(curSortType);
  313. });
  314. let tgBtn = $('.fe-feedback #toggleBtn').click(function (e) {
  315. e.preventDefault();
  316. e.stopPropagation();
  317. chrome.runtime.sendMessage({
  318. type: 'fh-dynamic-any-thing'
  319. }, (params) => {
  320. let show = String(localStorage.getItem(JSON_TOOL_BAR_ALWAYS_SHOW)) !== 'false';
  321. localStorage.setItem(JSON_TOOL_BAR_ALWAYS_SHOW, !show);
  322. let toolBarClassList = document.querySelector('div.x-toolbar').classList;
  323. if (!show) {
  324. toolBarClassList.remove('t-collapse');
  325. tgBtn.html('隐藏&gt;&gt;');
  326. } else {
  327. toolBarClassList.add('t-collapse');
  328. tgBtn.html('&lt;&lt;');
  329. }
  330. });
  331. });
  332. chrome.runtime.sendMessage({
  333. type: 'fh-dynamic-any-thing'
  334. }, params => {
  335. let show = String(localStorage.getItem(JSON_TOOL_BAR_ALWAYS_SHOW)) !== 'false';
  336. let toolBarClassList = document.querySelector('div.x-toolbar').classList;
  337. if (show) {
  338. toolBarClassList.remove('t-collapse');
  339. tgBtn.html('隐藏&gt;&gt;');
  340. } else {
  341. toolBarClassList.add('t-collapse');
  342. tgBtn.html('&lt;&lt;');
  343. }
  344. });
  345. $('#jsonGetCorrectCnt').click(function (e) {
  346. _getCorrectContent();
  347. });
  348. $('#capturePage').click(function (e) {
  349. chrome.runtime.sendMessage({
  350. type: 'capture-visible-page'
  351. }, uri => {
  352. window.open(uri);
  353. });
  354. e.preventDefault();
  355. e.stopPropagation();
  356. });
  357. };
  358. return {
  359. format: (options) => {
  360. setTimeout(() => {
  361. _format(options);
  362. }, pleaseLetJsLoaded);
  363. }
  364. };
  365. })();