format-lib.js 30 KB

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