format-lib.js 28 KB

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