format-lib.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. /**
  2. * FeHelper Json Format Lib
  3. */
  4. let JsonFormatEntrance = (function () {
  5. "use strict";
  6. let jfContent,
  7. jfPre,
  8. jfStyleEl,
  9. jfOptEl,
  10. jfPathEl,
  11. formattingMsg;
  12. let lastKvovIdGiven = 0;
  13. let cachedJsonString = '';
  14. let _initElements = function () {
  15. jfContent = $('#jfContent');
  16. if (!jfContent[0]) {
  17. jfContent = $('<div id="jfContent" />').appendTo('body');
  18. }
  19. jfPre = $('#jfContent_pre');
  20. if (!jfPre[0]) {
  21. jfPre = $('<pre id="jfContent_pre" />').appendTo('body');
  22. }
  23. jfStyleEl = $('#jfStyleEl');
  24. if (!jfStyleEl[0]) {
  25. jfStyleEl = $('<style id="jfStyleEl" />').appendTo('head');
  26. }
  27. formattingMsg = $('#formattingMsg');
  28. if (!formattingMsg[0]) {
  29. formattingMsg = $('<div id="formattingMsg"><span class="x-loading"></span>格式化中...</div>').appendTo('body');
  30. }
  31. jfOptEl = $('#boxOpt');
  32. if (!jfOptEl.length) {
  33. jfOptEl = $('<div id="boxOpt"><a class="opt-download" target="_blank">下载</a>|<a class="opt-copy">复制</a>|<a class="opt-del">删除</a></div>').appendTo('body');
  34. }
  35. try {
  36. jfContent.html('').show();
  37. jfPre.html('').hide();
  38. jfOptEl && jfOptEl.hide();
  39. jfPathEl && jfPathEl.hide();
  40. formattingMsg.hide();
  41. } catch (e) {
  42. }
  43. };
  44. // Add listener to receive response from BG when ready
  45. let postMessage = function (msg) {
  46. switch (msg[0]) {
  47. case 'NOT JSON' :
  48. jfPre.show();
  49. jfContent.html('<span class="x-json-tips">JSON不合法,请检查:</span>');
  50. break;
  51. case 'FORMATTING' :
  52. formattingMsg.show();
  53. break;
  54. case 'FORMATTED' :
  55. formattingMsg.hide();
  56. jfContent.html(msg[1]);
  57. _loadJs();
  58. _buildOptionBar();
  59. // 事件绑定
  60. _addEvents();
  61. // 支持文件下载
  62. _downloadSupport(cachedJsonString);
  63. break;
  64. default :
  65. throw new Error('Message not understood: ' + msg[0]);
  66. }
  67. };
  68. /**
  69. * HTML特殊字符格式化
  70. * @param str
  71. * @returns {*}
  72. */
  73. let htmlspecialchars = function (str) {
  74. str = str.replace(/&/g, '&amp;');
  75. str = str.replace(/</g, '&lt;');
  76. str = str.replace(/>/g, '&gt;');
  77. str = str.replace(/"/g, '&quot;');
  78. str = str.replace(/'/g, '&#039;');
  79. return str;
  80. };
  81. /**
  82. * 执行代码格式化
  83. * @param {[type]} jsonStr [description]
  84. * @return {[type]}
  85. */
  86. let format = function (jsonStr) {
  87. cachedJsonString = JSON.stringify(JSON.parse(jsonStr), null, 4);
  88. _initElements();
  89. jfPre.html(htmlspecialchars(cachedJsonString));
  90. JsonFormatDealer.postMessage({
  91. type: "SENDING TEXT",
  92. text: jsonStr,
  93. length: jsonStr.length
  94. });
  95. };
  96. let _loadJs = function () {
  97. if (typeof Tarp === 'object') {
  98. Tarp.require('../static/js/utils.js');
  99. } else {
  100. toast('无法加载Tarp.require.js');
  101. }
  102. };
  103. /**
  104. * 直接下载,能解决中文乱码
  105. * @param content
  106. * @private
  107. */
  108. let _downloadSupport = function (content) {
  109. // 下载链接
  110. let dt = (new Date()).format('yyyyMMddHHmmss');
  111. let blob = new Blob([content], {type: 'application/octet-stream'});
  112. let button = $('<button id="btnDownload">下载JSON</button>').appendTo('#optionBar');
  113. if (typeof chrome === 'undefined' || !chrome.permissions) {
  114. button.click(function (e) {
  115. let aLink = $('#aLinkDownload');
  116. if (!aLink[0]) {
  117. aLink = $('<a id="aLinkDownload" target="_blank" title="保存到本地">下载JSON数据</a>').appendTo('body');
  118. aLink.attr('download', 'FeHelper-' + dt + '.json');
  119. aLink.attr('href', URL.createObjectURL(blob));
  120. }
  121. aLink[0].click();
  122. });
  123. } else {
  124. button.click(function (e) {
  125. // 请求权限
  126. chrome.permissions.request({
  127. permissions: ['downloads']
  128. }, (granted) => {
  129. if (granted) {
  130. chrome.downloads.download({
  131. url: URL.createObjectURL(blob),
  132. saveAs: true,
  133. conflictAction: 'overwrite',
  134. filename: 'FeHelper-' + dt + '.json'
  135. });
  136. } else {
  137. toast('必须接受授权,才能正常下载!');
  138. }
  139. });
  140. });
  141. }
  142. };
  143. /**
  144. * chrome 下复制到剪贴板
  145. * @param text
  146. */
  147. let _copyToClipboard = function (text) {
  148. let input = document.createElement('textarea');
  149. input.style.position = 'fixed';
  150. input.style.opacity = 0;
  151. input.value = text;
  152. document.body.appendChild(input);
  153. input.select();
  154. document.execCommand('Copy');
  155. document.body.removeChild(input);
  156. toast('Json片段复制成功,随处粘贴可用!')
  157. };
  158. /**
  159. * 从el中获取json文本
  160. * @param el
  161. * @returns {string}
  162. */
  163. let getJsonText = function (el) {
  164. let txt = el.text().replace(/":\s/gm, '":').replace(/,$/, '').trim();
  165. if (!(/^{/.test(txt) && /\}$/.test(txt)) && !(/^\[/.test(txt) && /\]$/.test(txt))) {
  166. txt = '{' + txt + '}';
  167. }
  168. try {
  169. txt = JSON.stringify(JSON.parse(txt), null, 4);
  170. } catch (err) {
  171. }
  172. return txt;
  173. };
  174. /**
  175. * 给某个节点增加操作项
  176. * @param el
  177. * @private
  178. */
  179. let _addOptForItem = function (el) {
  180. // 下载json片段
  181. let fnDownload = function (ec) {
  182. let txt = getJsonText(el);
  183. // 下载片段
  184. let dt = (new Date()).format('yyyyMMddHHmmss');
  185. let blob = new Blob([txt], {type: 'application/octet-stream'});
  186. if (typeof chrome === 'undefined' || !chrome.permissions) {
  187. // 下载JSON的简单形式
  188. $(this).attr('download', 'FeHelper-' + dt + '.json').attr('href', URL.createObjectURL(blob));
  189. } else {
  190. // 请求权限
  191. chrome.permissions.request({
  192. permissions: ['downloads']
  193. }, (granted) => {
  194. if (granted) {
  195. chrome.downloads.download({
  196. url: URL.createObjectURL(blob),
  197. saveAs: true,
  198. conflictAction: 'overwrite',
  199. filename: 'FeHelper-' + dt + '.json'
  200. });
  201. } else {
  202. toast('必须接受授权,才能正常下载!');
  203. }
  204. });
  205. }
  206. };
  207. // 复制json片段
  208. let fnCopy = function (ec) {
  209. _copyToClipboard(getJsonText(el));
  210. };
  211. // 删除json片段
  212. let fnDel = function (ed) {
  213. if (el.parent().is('#formattedJson')) {
  214. toast('如果连最外层的Json也删掉的话,就没啥意义了哦!');
  215. return false;
  216. }
  217. toast('节点已删除成功!');
  218. el.remove();
  219. jfOptEl.css('top', -1000).hide();
  220. jfPathEl && jfPathEl.hide();
  221. };
  222. jfOptEl.find('a.opt-download').unbind('click').bind('click', fnDownload);
  223. jfOptEl.find('a.opt-copy').unbind('click').bind('click', fnCopy);
  224. jfOptEl.find('a.opt-del').unbind('click').bind('click', fnDel);
  225. jfOptEl.css({
  226. left: el.offset().left + el.width() - 90,
  227. top: el.offset().top
  228. }).show();
  229. };
  230. /**
  231. * 折叠所有
  232. * @param elements
  233. */
  234. function collapse(elements) {
  235. let el;
  236. $.each(elements, function (i) {
  237. el = $(this);
  238. if (el.children('.blockInner').length) {
  239. el.addClass('collapsed');
  240. if (!el.attr('id')) {
  241. el.attr('id', 'kvov' + (++lastKvovIdGiven));
  242. let count = el.children('.blockInner').eq(0).children().length;
  243. // Generate comment text eg "4 items"
  244. let comment = count + (count === 1 ? ' item' : ' items');
  245. // Add CSS that targets it
  246. jfStyleEl[0].insertAdjacentHTML(
  247. 'beforeend',
  248. '\n#kvov' + lastKvovIdGiven + '.collapsed:after{color: #aaa; content:" // ' + comment + '"}'
  249. );
  250. }
  251. }
  252. });
  253. }
  254. /**
  255. * 创建几个全局操作的按钮,置于页面右上角即可
  256. * @private
  257. */
  258. let _buildOptionBar = function () {
  259. let optionBar = $('#optionBar');
  260. if (optionBar) {
  261. optionBar.remove();
  262. }
  263. optionBar = $('<div id="optionBar" />').appendTo(jfContent.parent());
  264. let buttonFormatted = $('<button id="buttonFormatted">元数据</button>').appendTo(optionBar);
  265. let buttonCollapseAll = $('<button id="buttonCollapseAll">折叠所有</button>').appendTo(optionBar);
  266. let plainOn = false;
  267. buttonFormatted.bind('click', function (e) {
  268. if (plainOn) {
  269. plainOn = false;
  270. jfPre.hide();
  271. jfContent.show();
  272. buttonFormatted.text('元数据');
  273. } else {
  274. plainOn = true;
  275. jfPre.show();
  276. jfContent.hide();
  277. buttonFormatted.text('格式化');
  278. }
  279. jfOptEl && jfOptEl.hide();
  280. jfPathEl && jfPathEl.hide();
  281. });
  282. buttonCollapseAll.bind('click', function (e) {
  283. // 如果内容还没有格式化过,需要再格式化一下
  284. if (plainOn) {
  285. buttonFormatted.trigger('click');
  286. }
  287. if (buttonCollapseAll.text() === '折叠所有') {
  288. buttonCollapseAll.text('展开所有');
  289. collapse($('.objProp,.arrElem'));
  290. } else {
  291. buttonCollapseAll.text('折叠所有');
  292. $('.objProp,.arrElem').removeClass('collapsed');
  293. }
  294. jfOptEl && jfOptEl.hide();
  295. jfPathEl && jfPathEl.hide();
  296. });
  297. };
  298. // 显示当前节点的Key
  299. let _showJsonKey = function (curEl) {
  300. let keys = [];
  301. do {
  302. if (curEl.hasClass('arrElem')) {
  303. if (!curEl.hasClass('rootKvov')) {
  304. keys.unshift('[' + curEl.prevAll('.kvov').length + ']');
  305. }
  306. } else {
  307. keys.unshift(curEl.find('>.k').text());
  308. }
  309. if (curEl.parent().hasClass('rootKvov') || curEl.parent().parent().hasClass('rootKvov')) {
  310. break;
  311. }
  312. curEl = curEl.parent().parent();
  313. } while (curEl.length && !curEl.hasClass('rootKvov'));
  314. let path = keys.join('#@#').replace(/#@#\[/g, '[').replace(/#@#/g, '.');
  315. if (!jfPathEl) {
  316. jfPathEl = $('<div/>').css({
  317. position: 'fixed',
  318. bottom: 0,
  319. left: 0,
  320. background: 'rgb(0, 0, 0,0.6)',
  321. color: '#ff0',
  322. fontSize: '12px',
  323. fontWeight: 'bold',
  324. padding: '2px 10px 2px 2px'
  325. }).appendTo('body');
  326. }
  327. jfPathEl.html('当前路径:' + path).show();
  328. };
  329. // 附加操作
  330. let _addEvents = function () {
  331. // 折叠、展开
  332. $('#jfContent span.e').bind('click', function (ev) {
  333. ev.preventDefault();
  334. let parentEl = $(this).parent();
  335. parentEl.toggleClass('collapsed');
  336. if (parentEl.hasClass('collapsed')) {
  337. collapse(parentEl);
  338. }
  339. });
  340. // 点击选中:高亮
  341. $('#jfContent .kvov').bind('click', function (e) {
  342. if ($(this).hasClass('x-outline')) {
  343. jfOptEl && jfOptEl.hide();
  344. jfPathEl && jfPathEl.hide();
  345. $(this).removeClass('x-outline');
  346. e.stopPropagation();
  347. return true;
  348. }
  349. $('.x-outline').removeClass('x-outline');
  350. let el = $(this).removeClass('x-hover').addClass('x-outline');
  351. // 增加复制、删除功能
  352. _addOptForItem(el);
  353. // 显示key
  354. _showJsonKey(el);
  355. if (!$(e.target).is('.kvov .e')) {
  356. e.stopPropagation();
  357. } else {
  358. $(e.target).parent().trigger('click');
  359. }
  360. // 触发钩子
  361. if (typeof window._OnJsonItemClickByFH === 'function') {
  362. window._OnJsonItemClickByFH(getJsonText(el));
  363. }
  364. }).bind('mouseover', function (e) {
  365. $(this).addClass('x-hover');
  366. return false;
  367. }).bind('mouseout', function (e) {
  368. $(this).removeClass('x-hover');
  369. });
  370. };
  371. return {
  372. format: format,
  373. postMessage: postMessage
  374. }
  375. })();
  376. let JsonFormatDealer = (function () {
  377. "use strict";
  378. // Constants
  379. let
  380. TYPE_STRING = 1,
  381. TYPE_NUMBER = 2,
  382. TYPE_OBJECT = 3,
  383. TYPE_ARRAY = 4,
  384. TYPE_BOOL = 5,
  385. TYPE_NULL = 6
  386. ;
  387. // Utility functions
  388. function removeComments(str) {
  389. str = ('__' + str + '__').split('');
  390. let mode = {
  391. singleQuote: false,
  392. doubleQuote: false,
  393. regex: false,
  394. blockComment: false,
  395. lineComment: false,
  396. condComp: false
  397. };
  398. for (let i = 0, l = str.length; i < l; i++) {
  399. if (mode.regex) {
  400. if (str[i] === '/' && str[i - 1] !== '\\') {
  401. mode.regex = false;
  402. }
  403. continue;
  404. }
  405. if (mode.singleQuote) {
  406. if (str[i] === "'" && str[i - 1] !== '\\') {
  407. mode.singleQuote = false;
  408. }
  409. continue;
  410. }
  411. if (mode.doubleQuote) {
  412. if (str[i] === '"' && str[i - 1] !== '\\') {
  413. mode.doubleQuote = false;
  414. }
  415. continue;
  416. }
  417. if (mode.blockComment) {
  418. if (str[i] === '*' && str[i + 1] === '/') {
  419. str[i + 1] = '';
  420. mode.blockComment = false;
  421. }
  422. str[i] = '';
  423. continue;
  424. }
  425. if (mode.lineComment) {
  426. if (str[i + 1] === '\n' || str[i + 1] === '\r') {
  427. mode.lineComment = false;
  428. }
  429. str[i] = '';
  430. continue;
  431. }
  432. if (mode.condComp) {
  433. if (str[i - 2] === '@' && str[i - 1] === '*' && str[i] === '/') {
  434. mode.condComp = false;
  435. }
  436. continue;
  437. }
  438. mode.doubleQuote = str[i] === '"';
  439. mode.singleQuote = str[i] === "'";
  440. if (str[i] === '/') {
  441. if (str[i + 1] === '*' && str[i + 2] === '@') {
  442. mode.condComp = true;
  443. continue;
  444. }
  445. if (str[i + 1] === '*') {
  446. str[i] = '';
  447. mode.blockComment = true;
  448. continue;
  449. }
  450. if (str[i + 1] === '/') {
  451. str[i] = '';
  452. mode.lineComment = true;
  453. continue;
  454. }
  455. mode.regex = true;
  456. }
  457. }
  458. return str.join('').slice(2, -2);
  459. }
  460. // Template elements
  461. let templates,
  462. baseDiv = document.createElement('div'),
  463. baseSpan = document.createElement('span');
  464. function getSpanBoth(innerText, className) {
  465. let span = baseSpan.cloneNode(false);
  466. span.className = className;
  467. span.innerText = innerText;
  468. return span;
  469. }
  470. function getSpanText(innerText) {
  471. let span = baseSpan.cloneNode(false);
  472. span.innerText = innerText;
  473. return span;
  474. }
  475. function getSpanClass(className) {
  476. let span = baseSpan.cloneNode(false);
  477. span.className = className;
  478. return span;
  479. }
  480. function getDivClass(className) {
  481. let span = baseDiv.cloneNode(false);
  482. span.className = className;
  483. return span;
  484. }
  485. // Create template nodes
  486. let templatesObj = {
  487. t_kvov: getDivClass('kvov'),
  488. t_key: getSpanClass('k'),
  489. t_string: getSpanClass('s'),
  490. t_number: getSpanClass('n'),
  491. t_exp: getSpanClass('e'),
  492. t_null: getSpanBoth('null', 'nl'),
  493. t_true: getSpanBoth('true', 'bl'),
  494. t_false: getSpanBoth('false', 'bl'),
  495. t_oBrace: getSpanBoth('{', 'b'),
  496. t_cBrace: getSpanBoth('}', 'b'),
  497. t_oBracket: getSpanBoth('[', 'b'),
  498. t_cBracket: getSpanBoth(']', 'b'),
  499. t_ellipsis: getSpanClass('ell'),
  500. t_blockInner: getSpanClass('blockInner'),
  501. t_colonAndSpace: document.createTextNode(':\u00A0'),
  502. t_commaText: document.createTextNode(','),
  503. t_dblqText: document.createTextNode('"')
  504. };
  505. // Core recursive DOM-building function
  506. function getKvovDOM(value, keyName) {
  507. let type,
  508. kvov,
  509. nonZeroSize,
  510. templates = templatesObj, // bring into scope for tiny speed boost
  511. objKey,
  512. keySpan,
  513. valueElement
  514. ;
  515. // Establish value type
  516. if (typeof value === 'string')
  517. type = TYPE_STRING;
  518. else if (typeof value === 'number')
  519. type = TYPE_NUMBER;
  520. else if (value === false || value === true)
  521. type = TYPE_BOOL;
  522. else if (value === null)
  523. type = TYPE_NULL;
  524. else if (value instanceof Array)
  525. type = TYPE_ARRAY;
  526. else
  527. type = TYPE_OBJECT;
  528. // Root node for this kvov
  529. kvov = templates.t_kvov.cloneNode(false);
  530. // Add an 'expander' first (if this is object/array with non-zero size)
  531. if (type === TYPE_OBJECT || type === TYPE_ARRAY) {
  532. if (typeof JSON.BigNumber === 'function' && value instanceof JSON.BigNumber) {
  533. value = JSON.stringify(value);
  534. type = TYPE_NUMBER;
  535. } else {
  536. nonZeroSize = false;
  537. for (objKey in value) {
  538. if (value.hasOwnProperty(objKey)) {
  539. nonZeroSize = true;
  540. break; // no need to keep counting; only need one
  541. }
  542. }
  543. if (nonZeroSize)
  544. kvov.appendChild(templates.t_exp.cloneNode(true));
  545. }
  546. }
  547. // If there's a key, add that before the value
  548. if (keyName !== false) { // NB: "" is a legal keyname in JSON
  549. // This kvov must be an object property
  550. kvov.classList.add('objProp');
  551. // Create a span for the key name
  552. keySpan = templates.t_key.cloneNode(false);
  553. keySpan.textContent = JSON.stringify(keyName).slice(1, -1); // remove quotes
  554. // Add it to kvov, with quote marks
  555. kvov.appendChild(templates.t_dblqText.cloneNode(false));
  556. kvov.appendChild(keySpan);
  557. kvov.appendChild(templates.t_dblqText.cloneNode(false));
  558. // Also add ":&nbsp;" (colon and non-breaking space)
  559. kvov.appendChild(templates.t_colonAndSpace.cloneNode(false));
  560. }
  561. else {
  562. // This is an array element instead
  563. kvov.classList.add('arrElem');
  564. }
  565. // Generate DOM for this value
  566. let blockInner, childKvov;
  567. switch (type) {
  568. case TYPE_STRING:
  569. // If string is a URL, get a link, otherwise get a span
  570. let innerStringEl = baseSpan.cloneNode(false),
  571. escapedString = JSON.stringify(value);
  572. escapedString = escapedString.substring(1, escapedString.length - 1); // remove quotes
  573. if (value[0] === 'h' && value.substring(0, 4) === 'http') { // crude but fast - some false positives, but rare, and UX doesn't suffer terribly from them.
  574. let innerStringA = document.createElement('A');
  575. innerStringA.href = value;
  576. innerStringA.innerText = escapedString;
  577. innerStringEl.appendChild(innerStringA);
  578. }
  579. else {
  580. innerStringEl.innerText = escapedString;
  581. }
  582. valueElement = templates.t_string.cloneNode(false);
  583. valueElement.appendChild(templates.t_dblqText.cloneNode(false));
  584. valueElement.appendChild(innerStringEl);
  585. valueElement.appendChild(templates.t_dblqText.cloneNode(false));
  586. kvov.appendChild(valueElement);
  587. break;
  588. case TYPE_NUMBER:
  589. // Simply add a number element (span.n)
  590. valueElement = templates.t_number.cloneNode(false);
  591. valueElement.innerText = value;
  592. kvov.appendChild(valueElement);
  593. break;
  594. case TYPE_OBJECT:
  595. // Add opening brace
  596. kvov.appendChild(templates.t_oBrace.cloneNode(true));
  597. // If any properties, add a blockInner containing k/v pair(s)
  598. if (nonZeroSize) {
  599. // Add ellipsis (empty, but will be made to do something when kvov is collapsed)
  600. kvov.appendChild(templates.t_ellipsis.cloneNode(false));
  601. // Create blockInner, which indents (don't attach yet)
  602. blockInner = templates.t_blockInner.cloneNode(false);
  603. // For each key/value pair, add as a kvov to blockInner
  604. let count = 0, k, comma;
  605. for (k in value) {
  606. if (value.hasOwnProperty(k)) {
  607. count++;
  608. childKvov = getKvovDOM(value[k], k);
  609. // Add comma
  610. comma = templates.t_commaText.cloneNode();
  611. childKvov.appendChild(comma);
  612. blockInner.appendChild(childKvov);
  613. }
  614. }
  615. // Now remove the last comma
  616. childKvov.removeChild(comma);
  617. // Add blockInner
  618. kvov.appendChild(blockInner);
  619. }
  620. // Add closing brace
  621. kvov.appendChild(templates.t_cBrace.cloneNode(true));
  622. break;
  623. case TYPE_ARRAY:
  624. // Add opening bracket
  625. kvov.appendChild(templates.t_oBracket.cloneNode(true));
  626. // If non-zero length array, add blockInner containing inner vals
  627. if (nonZeroSize) {
  628. // Add ellipsis
  629. kvov.appendChild(templates.t_ellipsis.cloneNode(false));
  630. // Create blockInner (which indents) (don't attach yet)
  631. blockInner = templates.t_blockInner.cloneNode(false);
  632. // For each key/value pair, add the markup
  633. for (let i = 0, length = value.length, lastIndex = length - 1; i < length; i++) {
  634. // Make a new kvov, with no key
  635. childKvov = getKvovDOM(value[i], false);
  636. // Add comma if not last one
  637. if (i < lastIndex)
  638. childKvov.appendChild(templates.t_commaText.cloneNode());
  639. // Append the child kvov
  640. blockInner.appendChild(childKvov);
  641. }
  642. // Add blockInner
  643. kvov.appendChild(blockInner);
  644. }
  645. // Add closing bracket
  646. kvov.appendChild(templates.t_cBracket.cloneNode(true));
  647. break;
  648. case TYPE_BOOL:
  649. if (value)
  650. kvov.appendChild(templates.t_true.cloneNode(true));
  651. else
  652. kvov.appendChild(templates.t_false.cloneNode(true));
  653. break;
  654. case TYPE_NULL:
  655. kvov.appendChild(templates.t_null.cloneNode(true));
  656. break;
  657. }
  658. return kvov;
  659. }
  660. // Function to convert object to an HTML string
  661. function jsonObjToHTML(obj, jsonpFunctionName) {
  662. // spin(5) ;
  663. // Format object (using recursive kvov builder)
  664. let rootKvov = getKvovDOM(obj, false);
  665. // The whole DOM is now built.
  666. // Set class on root node to identify it
  667. rootKvov.classList.add('rootKvov');
  668. // Make div#formattedJson and append the root kvov
  669. let divFormattedJson = document.createElement('DIV');
  670. divFormattedJson.id = 'formattedJson';
  671. divFormattedJson.appendChild(rootKvov);
  672. // Convert it to an HTML string (shame about this step, but necessary for passing it through to the content page)
  673. let returnHTML = divFormattedJson.outerHTML;
  674. // Top and tail with JSONP padding if necessary
  675. if (jsonpFunctionName !== null) {
  676. returnHTML =
  677. '<div id="jsonpOpener">' + jsonpFunctionName + ' ( </div>' +
  678. returnHTML +
  679. '<div id="jsonpCloser">)</div>';
  680. }
  681. // Return the HTML
  682. return returnHTML;
  683. }
  684. // Listen for requests from content pages wanting to set up a port
  685. let postMessage = function (msg) {
  686. let jsonpFunctionName = null;
  687. if (msg.type === 'SENDING TEXT') {
  688. // Try to parse as JSON
  689. let obj,
  690. text = msg.text;
  691. try {
  692. obj = JSON.parse(text);
  693. }
  694. catch (e) {
  695. // Not JSON; could be JSONP though.
  696. // Try stripping 'padding' (if any), and try parsing it again
  697. text = text.trim();
  698. // Find where the first paren is (and exit if none)
  699. let indexOfParen;
  700. if (!(indexOfParen = text.indexOf('('))) {
  701. JsonFormatEntrance.postMessage(['NOT JSON', 'no opening parenthesis']);
  702. return;
  703. }
  704. // Get the substring up to the first "(", with any comments/whitespace stripped out
  705. let firstBit = removeComments(text.substring(0, indexOfParen)).trim();
  706. if (!firstBit.match(/^[a-zA-Z_$][\.\[\]'"0-9a-zA-Z_$]*$/)) {
  707. // The 'firstBit' is NOT a valid function identifier.
  708. JsonFormatEntrance.postMessage(['NOT JSON', 'first bit not a valid function name']);
  709. return;
  710. }
  711. // Find last parenthesis (exit if none)
  712. let indexOfLastParen;
  713. if (!(indexOfLastParen = text.lastIndexOf(')'))) {
  714. JsonFormatEntrance.postMessage(['NOT JSON', 'no closing paren']);
  715. return;
  716. }
  717. // Check that what's after the last parenthesis is just whitespace, comments, and possibly a semicolon (exit if anything else)
  718. let lastBit = removeComments(text.substring(indexOfLastParen + 1)).trim();
  719. if (lastBit !== "" && lastBit !== ';') {
  720. JsonFormatEntrance.postMessage(['NOT JSON', 'last closing paren followed by invalid characters']);
  721. return;
  722. }
  723. // So, it looks like a valid JS function call, but we don't know whether it's JSON inside the parentheses...
  724. // Check if the 'argument' is actually JSON (and record the parsed result)
  725. text = text.substring(indexOfParen + 1, indexOfLastParen);
  726. try {
  727. obj = JSON.parse(text);
  728. }
  729. catch (e2) {
  730. // Just some other text that happens to be in a function call.
  731. // Respond as not JSON, and exit
  732. JsonFormatEntrance.postMessage(['NOT JSON', 'looks like a function call, but the parameter is not valid JSON']);
  733. return;
  734. }
  735. jsonpFunctionName = firstBit;
  736. }
  737. // If still running, we now have obj, which is valid JSON.
  738. // Ensure it's not a number or string (technically valid JSON, but no point prettifying it)
  739. if (typeof obj !== 'object' && typeof obj !== 'array') {
  740. JsonFormatEntrance.postMessage(['NOT JSON', 'technically JSON but not an object or array']);
  741. return;
  742. }
  743. JsonFormatEntrance.postMessage(['FORMATTING']);
  744. try {
  745. // 有的页面设置了 安全策略,连localStorage都不能用,setTimeout开启多线程就更别说了
  746. localStorage.getItem('just test : Blocked script execution in xxx?');
  747. // 在非UI线程中操作:异步。。。
  748. setTimeout(function () {
  749. // Do formatting
  750. let html = jsonObjToHTML(obj, jsonpFunctionName);
  751. // Post the HTML string to the content script
  752. JsonFormatEntrance.postMessage(['FORMATTED', html]);
  753. }, 0);
  754. } catch (ex) {
  755. // 错误信息类似:Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag.
  756. let html = jsonObjToHTML(obj, jsonpFunctionName);
  757. JsonFormatEntrance.postMessage(['FORMATTED', html]);
  758. }
  759. }
  760. };
  761. return {
  762. postMessage: postMessage
  763. };
  764. })();
  765. module.exports = {
  766. format: JsonFormatEntrance.format
  767. };