regexp-tester.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. /* global showHelp */
  2. 'use strict';
  3. // eslint-disable-next-line no-var
  4. var regExpTester = (() => {
  5. const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain=';
  6. const OWN_ICON = chrome.runtime.getManifest().icons['16'];
  7. const cachedRegexps = new Map();
  8. let currentRegexps = [];
  9. let isInit = false;
  10. function init() {
  11. isInit = true;
  12. chrome.tabs.onUpdated.addListener(onTabUpdate);
  13. }
  14. function uninit() {
  15. chrome.tabs.onUpdated.removeListener(onTabUpdate);
  16. isInit = false;
  17. }
  18. function onTabUpdate(tabId, info) {
  19. if (info.url) {
  20. update();
  21. }
  22. }
  23. function isShowed() {
  24. return Boolean($('.regexp-report'));
  25. }
  26. function toggle(state = !isShowed()) {
  27. if (state && !isShowed()) {
  28. if (!isInit) {
  29. init();
  30. }
  31. showHelp('', $element({className: 'regexp-report'}));
  32. } else if (!state && isShowed()) {
  33. if (isInit) {
  34. uninit();
  35. }
  36. // TODO: need a closeHelp function
  37. $('#help-popup .dismiss').onclick();
  38. }
  39. }
  40. function update(newRegexps) {
  41. if (!isShowed()) {
  42. if (isInit) {
  43. uninit();
  44. }
  45. return;
  46. }
  47. if (newRegexps) {
  48. currentRegexps = newRegexps;
  49. }
  50. const regexps = currentRegexps.map(text => {
  51. const rxData = Object.assign({text}, cachedRegexps.get(text));
  52. if (!rxData.urls) {
  53. cachedRegexps.set(text, Object.assign(rxData, {
  54. // imitate buggy Stylish-for-chrome, see detectSloppyRegexps()
  55. rx: tryRegExp('^' + text + '$'),
  56. urls: new Map(),
  57. }));
  58. }
  59. return rxData;
  60. });
  61. const getMatchInfo = m => m && {text: m[0], pos: m.index};
  62. queryTabs().then(tabs => {
  63. const supported = tabs.map(tab => tab.url)
  64. .filter(url => URLS.supported(url));
  65. const unique = [...new Set(supported).values()];
  66. for (const rxData of regexps) {
  67. const {rx, urls} = rxData;
  68. if (rx) {
  69. const urlsNow = new Map();
  70. for (const url of unique) {
  71. const match = urls.get(url) || getMatchInfo(url.match(rx));
  72. if (match) {
  73. urlsNow.set(url, match);
  74. }
  75. }
  76. rxData.urls = urlsNow;
  77. }
  78. }
  79. const stats = {
  80. full: {data: [], label: t('styleRegexpTestFull')},
  81. partial: {data: [], label: [
  82. t('styleRegexpTestPartial'),
  83. template.regexpTestPartial.cloneNode(true),
  84. ]},
  85. none: {data: [], label: t('styleRegexpTestNone')},
  86. invalid: {data: [], label: t('styleRegexpTestInvalid')},
  87. };
  88. // collect stats
  89. for (const {text, rx, urls} of regexps) {
  90. if (!rx) {
  91. stats.invalid.data.push({text});
  92. continue;
  93. }
  94. if (!urls.size) {
  95. stats.none.data.push({text});
  96. continue;
  97. }
  98. const full = [];
  99. const partial = [];
  100. for (const [url, match] of urls.entries()) {
  101. const faviconUrl = url.startsWith(URLS.ownOrigin)
  102. ? OWN_ICON
  103. : GET_FAVICON_URL + new URL(url).hostname;
  104. const icon = $element({tag: 'img', src: faviconUrl});
  105. if (match.text.length === url.length) {
  106. full.push($element({appendChild: [
  107. icon,
  108. url,
  109. ]}));
  110. } else {
  111. partial.push($element({appendChild: [
  112. icon,
  113. url.substr(0, match.pos),
  114. $element({tag: 'mark', textContent: match.text}),
  115. url.substr(match.pos + match.text.length),
  116. ]}));
  117. }
  118. }
  119. if (full.length) {
  120. stats.full.data.push({text, urls: full});
  121. }
  122. if (partial.length) {
  123. stats.partial.data.push({text, urls: partial});
  124. }
  125. }
  126. // render stats
  127. const report = $element({className: 'regexp-report'});
  128. const br = $element({tag: 'br'});
  129. for (const type in stats) {
  130. // top level groups: full, partial, none, invalid
  131. const {label, data} = stats[type];
  132. if (!data.length) {
  133. continue;
  134. }
  135. const block = report.appendChild($element({
  136. tag: 'details',
  137. open: true,
  138. dataset: {type},
  139. appendChild: $element({tag: 'summary', appendChild: label}),
  140. }));
  141. // 2nd level: regexp text
  142. for (const {text, urls} of data) {
  143. if (urls) {
  144. // type is partial or full
  145. block.appendChild($element({
  146. tag: 'details',
  147. open: true,
  148. appendChild: [
  149. $element({tag: 'summary', textContent: text}),
  150. // 3rd level: tab urls
  151. ...urls,
  152. ],
  153. }));
  154. } else {
  155. // type is none or invalid
  156. block.appendChild(document.createTextNode(text));
  157. block.appendChild(br.cloneNode());
  158. }
  159. }
  160. }
  161. showHelp(t('styleRegexpTestTitle'), report);
  162. $('.regexp-report').onclick = event => {
  163. const target = event.target.closest('a, .regexp-report div');
  164. if (target) {
  165. openURL({
  166. url: target.href || target.textContent,
  167. currentWindow: null,
  168. });
  169. event.preventDefault();
  170. }
  171. };
  172. });
  173. }
  174. return {toggle, update};
  175. })();