You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

415 lines
14 KiB

10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
  1. /*******************************************************************************
  2. µMatrix - a Chromium browser extension to black/white list requests.
  3. Copyright (C) 2014 Raymond Hill
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see {http://www.gnu.org/licenses/}.
  14. Home: https://github.com/gorhill/uMatrix
  15. */
  16. /* global messaging, uDom */
  17. /******************************************************************************/
  18. (function() {
  19. /******************************************************************************/
  20. messaging.start('info.js');
  21. var targetUrl = 'all';
  22. var maxRequests = 500;
  23. var cachedUserSettings = {};
  24. /******************************************************************************/
  25. // Get a list of latest net requests
  26. function updateRequestData(callback) {
  27. var onResponseReceived = function(r) {
  28. var requests = [];
  29. for ( var pageURL in r ) {
  30. if ( r.hasOwnProperty(pageURL) === false ) {
  31. continue;
  32. }
  33. requests = requests.concat(r[pageURL]);
  34. }
  35. requests = requests
  36. .sort(function(a,b){return b.when-a.when;})
  37. .slice(0, maxRequests);
  38. callback(requests);
  39. };
  40. var request = {
  41. what: 'getRequestLogs',
  42. pageURL: targetUrl !== 'all' ? targetUrl : null
  43. };
  44. messaging.ask(request, onResponseReceived);
  45. }
  46. /******************************************************************************/
  47. function clearRequestData() {
  48. var request = {
  49. what: 'clearRequestLogs',
  50. pageURL: targetUrl !== 'all' ? targetUrl : null
  51. };
  52. messaging.tell(request);
  53. }
  54. /******************************************************************************/
  55. function renderNumber(value) {
  56. if ( isNaN(value) ) {
  57. return '0';
  58. }
  59. return value.toLocaleString();
  60. }
  61. /******************************************************************************/
  62. function renderNumbers(set) {
  63. var keys = Object.keys(set);
  64. var i = keys.length;
  65. var key;
  66. while ( i-- ) {
  67. key = keys[i];
  68. uDom(key).text(renderNumber(set[key]));
  69. }
  70. }
  71. /******************************************************************************/
  72. var renderLocalized = function(id, map) {
  73. var uElem = uDom('#' + id);
  74. var msg = chrome.i18n.getMessage(id);
  75. for ( var k in map ) {
  76. if ( map.hasOwnProperty(k) === false ) {
  77. continue;
  78. }
  79. msg = msg.replace('{{' + k + '}}', map[k]);
  80. }
  81. uElem.html(msg);
  82. };
  83. /******************************************************************************/
  84. function renderPageUrls() {
  85. var onResponseReceived = function(r) {
  86. var i, n;
  87. var select = uDom('#selectPageUrls');
  88. // Remove whatever was put there in a previous call
  89. uDom('#selectPageUrls > option').remove();
  90. var builtinOptions = uDom('#selectPageUrlsTemplate > option');
  91. n = builtinOptions.length;
  92. for ( i = 0; i < n; i++ ) {
  93. option = builtinOptions.at(i).clone();
  94. if ( option.val() === targetUrl ) {
  95. option.attr('selected', true);
  96. }
  97. select.append(option);
  98. }
  99. var pageURLs = r.pageURLs.sort();
  100. var pageURL, option;
  101. n = pageURLs.length;
  102. for ( i = 0; i < n; i++ ) {
  103. pageURL = pageURLs[i];
  104. // Behind-the-scene entry is always present, no need to recreate it
  105. if ( pageURL === r.behindTheSceneURL ) {
  106. continue;
  107. }
  108. option = uDom('<option>');
  109. option.val(pageURL);
  110. option.text(pageURL);
  111. if ( pageURL === targetUrl ) {
  112. option.attr('selected', true);
  113. }
  114. select.append(option);
  115. }
  116. // Deselect whatever is currently selected
  117. //uDom('#selectPageUrls > option:selected').prop('selected', false);
  118. // Select whatever needs to be selected
  119. //uDom('#selectPageUrls > option[value="'+targetUrl+'"]').prop('selected', true);
  120. };
  121. messaging.ask({ what: 'getPageURLs' }, onResponseReceived);
  122. }
  123. /******************************************************************************/
  124. function renderStats() {
  125. var onResponseReceived = function(r) {
  126. if ( !r.pageNetStats ) {
  127. targetUrl = 'all';
  128. }
  129. var requestStats = targetUrl === 'all' ? r.globalNetStats : r.pageNetStats;
  130. var blockedStats = requestStats.blocked;
  131. var allowedStats = requestStats.allowed;
  132. renderLocalized('statsPageCookieHeadersFoiled', { count: renderNumber(r.cookieHeaderFoiledCounter) });
  133. renderLocalized('statsPageRefererHeadersFoiled', { count: renderNumber(r.refererHeaderFoiledCounter) });
  134. renderLocalized('statsPageHyperlinkAuditingFoiled', { count: renderNumber(r.hyperlinkAuditingFoiledCounter) });
  135. renderLocalized('statsPageCookiesRemoved', { count: renderNumber(r.cookieRemovedCounter) });
  136. renderLocalized('statsPageLocalStoragesCleared', { count: renderNumber(r.localStorageRemovedCounter) });
  137. renderLocalized('statsPageBrowserCacheCleared', { count: renderNumber(r.browserCacheClearedCounter) });
  138. renderNumbers({
  139. '#blockedAllCount': requestStats.blocked.all,
  140. '#blockedMainFrameCount': blockedStats.doc,
  141. '#blockedCookieCount': blockedStats.cookie,
  142. '#blockedStylesheetCount': blockedStats.css,
  143. '#blockedImageCount': blockedStats.image,
  144. '#blockedObjectCount': blockedStats.plugin,
  145. '#blockedScriptCount': blockedStats.script,
  146. '#blockedXHRCount': blockedStats.xhr,
  147. '#blockedSubFrameCount': blockedStats.frame,
  148. '#blockedOtherCount': blockedStats.other,
  149. '#allowedAllCount': allowedStats.all,
  150. '#allowedMainFrameCount': allowedStats.doc,
  151. '#allowedCookieCount': allowedStats.cookie,
  152. '#allowedStylesheetCount': allowedStats.css,
  153. '#allowedImageCount': allowedStats.image,
  154. '#allowedObjectCount': allowedStats.plugin,
  155. '#allowedScriptCount': allowedStats.script,
  156. '#allowedXHRCount': allowedStats.xhr,
  157. '#allowedSubFrameCount': allowedStats.frame,
  158. '#allowedOtherCount': allowedStats.other
  159. });
  160. // because some i18n messages may contain links
  161. uDom('a').attr('target', '_blank');
  162. };
  163. messaging.ask({
  164. what: 'getStats',
  165. pageURL: targetUrl === 'all' ? null : targetUrl
  166. },
  167. onResponseReceived
  168. );
  169. }
  170. /******************************************************************************/
  171. function renderRequestRow(row, request) {
  172. row.attr('id', '');
  173. row.css('display', '');
  174. row.removeClass();
  175. if ( request.block !== false ) {
  176. row.addClass('blocked-true');
  177. } else {
  178. row.addClass('blocked-false');
  179. }
  180. row.addClass('type-' + request.type);
  181. var cells = row.descendants('td');
  182. // when
  183. var when = new Date(request.when);
  184. cells.at(0).text(when.toLocaleTimeString());
  185. // request type
  186. cells.at(1).text(request.type);
  187. // Well I got back full control since not using Tempo.js, I can now
  188. // generate smarter hyperlinks, that is, not hyperlinking fake
  189. // request URLs, which are recognizable with their curly braces inside.
  190. var a = cells.at(2).descendants('a');
  191. if ( request.url.search('{') < 0 ) {
  192. a.attr('href', request.url);
  193. a.css('display', '');
  194. } else {
  195. a.css('display', 'none');
  196. }
  197. // request URL
  198. cells.at(3).text(request.url);
  199. }
  200. /******************************************************************************/
  201. var renderRequests = function(requests) {
  202. var table = uDom('#requestsTable');
  203. var i, row;
  204. var rowTemplate = table.descendants('#requestRowTemplate').first();
  205. // Reuse whatever rows is already in there.
  206. var rows = table.descendants('tr:not(.ro)');
  207. var n = Math.min(requests.length, rows.length);
  208. for ( i = 0; i < n; i++ ) {
  209. renderRequestRow(rows.at(i), requests[i]);
  210. }
  211. // Unhide reused rows
  212. rows.subset(0, n).removeClass('unused');
  213. // Hide extra rows
  214. rows.subset(n).addClass('unused');
  215. // Create new rows to receive what is left
  216. n = requests.length;
  217. for ( ; i < n; i++ ) {
  218. row = rowTemplate.clone();
  219. renderRequestRow(row, requests[i]);
  220. row.insertBefore(rowTemplate);
  221. }
  222. syncWithFilters();
  223. };
  224. /******************************************************************************/
  225. var updateRequests = function() {
  226. updateRequestData(renderRequests);
  227. };
  228. /******************************************************************************/
  229. var clearRequests = function() {
  230. clearRequestData();
  231. renderRequests([]);
  232. };
  233. /******************************************************************************/
  234. function changeUserSettings(name, value) {
  235. cachedUserSettings[name] = value;
  236. messaging.tell({
  237. what: 'userSettings',
  238. name: name,
  239. value: value
  240. });
  241. }
  242. /******************************************************************************/
  243. function changeValueHandler(elem, setting, min, max) {
  244. var oldVal = cachedUserSettings[setting];
  245. var newVal = Math.round(parseFloat(elem.val()));
  246. if ( typeof newVal !== 'number' ) {
  247. newVal = oldVal;
  248. } else {
  249. newVal = Math.max(newVal, min);
  250. newVal = Math.min(newVal, max);
  251. }
  252. elem.val(newVal);
  253. if ( newVal !== oldVal ) {
  254. changeUserSettings(setting, newVal);
  255. }
  256. }
  257. /******************************************************************************/
  258. function changeFilterHandler() {
  259. // Save new state of filters in user settings
  260. // Initialize request filters as per user settings:
  261. // https://github.com/gorhill/httpswitchboard/issues/49
  262. var statsFilters = cachedUserSettings.statsFilters;
  263. uDom('input[id^="show-"][type="checkbox"]').forEach(function(input) {
  264. statsFilters[input.attr('id')] = !!input.prop('checked');
  265. });
  266. changeUserSettings('statsFilters', statsFilters);
  267. syncWithFilters();
  268. }
  269. /******************************************************************************/
  270. // Synchronize list of net requests with filter states
  271. function syncWithFilters() {
  272. var blocked = ['blocked','allowed'];
  273. var type = ['doc','cookie','css','image','plugin','script','xhr','frame','other'];
  274. var i = blocked.length;
  275. var j;
  276. var display, selector;
  277. while ( i-- ) {
  278. j = type.length;
  279. while ( j-- ) {
  280. display = uDom('#show-' + blocked[i]).prop('checked') &&
  281. uDom('#show-' + type[j]).prop('checked') ? '' : 'none';
  282. selector = '.blocked-' + (blocked[i] === 'blocked') + '.type-' + type[j];
  283. uDom(selector).css('display', display);
  284. }
  285. }
  286. }
  287. /******************************************************************************/
  288. var renderTransientTimer;
  289. function renderTransientData(internal) {
  290. // This is in case this function is not called from timeout event
  291. if ( internal && renderTransientTimer ) {
  292. clearTimeout(renderTransientTimer);
  293. }
  294. renderPageUrls();
  295. renderStats();
  296. renderTransientTimer = setTimeout(renderTransientData, 10000); // every 10s
  297. }
  298. /******************************************************************************/
  299. function targetUrlChangeHandler() {
  300. targetUrl = this[this.selectedIndex].value;
  301. renderStats();
  302. updateRequests();
  303. }
  304. /******************************************************************************/
  305. function prepareToDie() {
  306. changeValueHandler(uDom('#max-logged-requests'), 'maxLoggedRequests', 0, 999);
  307. }
  308. /******************************************************************************/
  309. var installEventHandlers = function() {
  310. uDom('#refreshRequests').on('click', updateRequests);
  311. uDom('#clearRequests').on('click', clearRequests);
  312. uDom('input[id^="show-"][type="checkbox"]').on('change', changeFilterHandler);
  313. uDom('#selectPageUrls').on('change', targetUrlChangeHandler);
  314. uDom('#max-logged-requests').on('change', function(){ changeValueHandler(uDom(this), 'maxLoggedRequests', 0, 999); });
  315. // https://github.com/gorhill/httpswitchboard/issues/197
  316. window.addEventListener('beforeunload', prepareToDie);
  317. };
  318. /******************************************************************************/
  319. uDom.onLoad(function(){
  320. // Initialize request filters as per user settings:
  321. // https://github.com/gorhill/httpswitchboard/issues/49
  322. var onResponseReceived = function(userSettings) {
  323. // cache a copy
  324. cachedUserSettings = userSettings;
  325. // init ui as per user settings
  326. uDom('#max-logged-requests').val(userSettings.maxLoggedRequests);
  327. var statsFilters = userSettings.statsFilters;
  328. uDom('input[id^="show-"][type="checkbox"]').forEach(function(input) {
  329. var filter = statsFilters[input.attr('id')];
  330. input.prop('checked', filter === undefined || filter === true);
  331. });
  332. installEventHandlers();
  333. };
  334. messaging.ask({ what: 'getUserSettings' }, onResponseReceived);
  335. renderTransientData(true);
  336. updateRequests();
  337. });
  338. /******************************************************************************/
  339. })();