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.

409 lines
13 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
  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 chrome, messaging, uDom */
  17. /******************************************************************************/
  18. (function() {
  19. /******************************************************************************/
  20. var listDetails = {};
  21. var externalHostsFiles = '';
  22. var cacheWasPurged = false;
  23. var needUpdate = false;
  24. var hasCachedContent = false;
  25. var re3rdPartyExternalAsset = /^https?:\/\/[a-z0-9]+/;
  26. var re3rdPartyRepoAsset = /^assets\/thirdparties\/([^\/]+)/;
  27. /******************************************************************************/
  28. messaging.start('hosts-files.js');
  29. var onMessage = function(msg) {
  30. switch ( msg.what ) {
  31. case 'loadHostsFilesCompleted':
  32. renderBlacklists();
  33. break;
  34. default:
  35. break;
  36. }
  37. };
  38. messaging.listen(onMessage);
  39. /******************************************************************************/
  40. // TODO: get rid of background page dependencies
  41. var renderBlacklists = function() {
  42. uDom('body').toggleClass('busy', true);
  43. // Assemble a pretty blacklist name if possible
  44. var listNameFromListKey = function(listKey) {
  45. var list = listDetails.current[listKey] || listDetails.available[listKey];
  46. var listTitle = list ? list.title : '';
  47. if ( listTitle === '' ) {
  48. return listKey;
  49. }
  50. return listTitle;
  51. };
  52. // Assemble a pretty blacklist name if possible
  53. var htmlFromHomeURL = function(blacklistHref) {
  54. if ( blacklistHref.indexOf('assets/thirdparties/') !== 0 ) {
  55. return '';
  56. }
  57. var matches = re3rdPartyRepoAsset.exec(blacklistHref);
  58. if ( matches === null || matches.length !== 2 ) {
  59. return '';
  60. }
  61. var hostname = matches[1];
  62. var domain = hostname;
  63. if ( domain === '' ) {
  64. return '';
  65. }
  66. var html = [
  67. ' <a href="http://',
  68. hostname,
  69. '" target="_blank">(',
  70. domain,
  71. ')</a>'
  72. ];
  73. return html.join('');
  74. };
  75. var purgeButtontext = chrome.i18n.getMessage('hostsFilesExternalListPurge');
  76. var updateButtontext = chrome.i18n.getMessage('hostsFilesExternalListNew');
  77. var obsoleteButtontext = chrome.i18n.getMessage('hostsFilesExternalListObsolete');
  78. var liTemplate = [
  79. '<li class="listDetails">',
  80. '<input type="checkbox" {{checked}}>',
  81. ' ',
  82. '<a href="{{URL}}" type="text/plain">',
  83. '{{name}}',
  84. '\u200E</a>',
  85. '{{homeURL}}',
  86. ': ',
  87. '<span class="dim">',
  88. chrome.i18n.getMessage('hostsFilesPerFileStats'),
  89. '</span>'
  90. ].join('');
  91. var htmlFromLeaf = function(listKey) {
  92. var html = [];
  93. var hostsEntry = listDetails.available[listKey];
  94. var li = liTemplate
  95. .replace('{{checked}}', hostsEntry.off ? '' : 'checked')
  96. .replace('{{URL}}', encodeURI(listKey))
  97. .replace('{{name}}', listNameFromListKey(listKey))
  98. .replace('{{homeURL}}', htmlFromHomeURL(listKey))
  99. .replace('{{used}}', !hostsEntry.off && !isNaN(+hostsEntry.entryUsedCount) ? hostsEntry.entryUsedCount.toLocaleString() : '0')
  100. .replace('{{total}}', !isNaN(+hostsEntry.entryCount) ? hostsEntry.entryCount.toLocaleString() : '?');
  101. html.push(li);
  102. // https://github.com/gorhill/uBlock/issues/104
  103. var asset = listDetails.cache[listKey];
  104. if ( asset === undefined ) {
  105. return html.join('\n');
  106. }
  107. // Update status
  108. if ( hostsEntry.off !== true ) {
  109. var obsolete = asset.repoObsolete ||
  110. asset.cacheObsolete ||
  111. asset.cached !== true && re3rdPartyExternalAsset.test(listKey);
  112. if ( obsolete ) {
  113. html.push(
  114. '&ensp;',
  115. '<span class="status obsolete">',
  116. asset.repoObsolete ? updateButtontext : obsoleteButtontext,
  117. '</span>'
  118. );
  119. needUpdate = true;
  120. }
  121. }
  122. // In cache
  123. if ( asset.cached ) {
  124. html.push(
  125. '&ensp;',
  126. '<span class="status purge">',
  127. purgeButtontext,
  128. '</span>'
  129. );
  130. hasCachedContent = true;
  131. }
  132. return html.join('\n');
  133. };
  134. var onListsReceived = function(details) {
  135. // Before all, set context vars
  136. listDetails = details;
  137. needUpdate = false;
  138. hasCachedContent = false;
  139. // Visually split the filter lists in two groups: built-in and external
  140. var htmlBuiltin = [];
  141. var htmlExternal = [];
  142. var hostsPaths = Object.keys(details.available);
  143. var hostsPath, hostsEntry;
  144. for ( var i = 0; i < hostsPaths.length; i++ ) {
  145. hostsPath = hostsPaths[i];
  146. hostsEntry = details.available[hostsPath];
  147. if ( hostsEntry.external ) {
  148. htmlExternal.push(htmlFromLeaf(hostsPath, hostsEntry));
  149. } else {
  150. htmlBuiltin.push(htmlFromLeaf(hostsPath, hostsEntry));
  151. }
  152. }
  153. if ( htmlExternal.length !== 0 ) {
  154. htmlBuiltin.push('<li>&nbsp;');
  155. }
  156. var html = htmlBuiltin.concat(htmlExternal);
  157. uDom('#listsOfBlockedHostsPrompt').text(
  158. chrome.i18n.getMessage('hostsFilesStats')
  159. .replace('{{blockedHostnameCount}}', details.blockedHostnameCount.toLocaleString())
  160. );
  161. uDom('#autoUpdate').prop('checked', listDetails.autoUpdate === true);
  162. uDom('#lists').html(html.join(''));
  163. uDom('a').attr('target', '_blank');
  164. updateWidgets();
  165. };
  166. messaging.ask({ what: 'getLists' }, onListsReceived);
  167. };
  168. /******************************************************************************/
  169. // Return whether selection of lists changed.
  170. var listsSelectionChanged = function() {
  171. if ( cacheWasPurged ) {
  172. return true;
  173. }
  174. var availableLists = listDetails.available;
  175. var currentLists = listDetails.current;
  176. var location, availableOff, currentOff;
  177. // This check existing entries
  178. for ( location in availableLists ) {
  179. if ( availableLists.hasOwnProperty(location) === false ) {
  180. continue;
  181. }
  182. availableOff = availableLists[location].off === true;
  183. currentOff = currentLists[location] === undefined || currentLists[location].off === true;
  184. if ( availableOff !== currentOff ) {
  185. return true;
  186. }
  187. }
  188. // This check removed entries
  189. for ( location in currentLists ) {
  190. if ( currentLists.hasOwnProperty(location) === false ) {
  191. continue;
  192. }
  193. currentOff = currentLists[location].off === true;
  194. availableOff = availableLists[location] === undefined || availableLists[location].off === true;
  195. if ( availableOff !== currentOff ) {
  196. return true;
  197. }
  198. }
  199. return false;
  200. };
  201. /******************************************************************************/
  202. // Return whether content need update.
  203. var listsContentChanged = function() {
  204. return needUpdate;
  205. };
  206. /******************************************************************************/
  207. // This is to give a visual hint that the selection of blacklists has changed.
  208. var updateWidgets = function() {
  209. uDom('#buttonApply').toggleClass('disabled', !listsSelectionChanged());
  210. uDom('#buttonUpdate').toggleClass('disabled', !listsContentChanged());
  211. uDom('#buttonPurgeAll').toggleClass('disabled', !hasCachedContent);
  212. uDom('body').toggleClass('busy', false);
  213. };
  214. /******************************************************************************/
  215. var onListCheckboxChanged = function() {
  216. var href = uDom(this).parent().descendants('a').first().attr('href');
  217. if ( typeof href !== 'string' ) {
  218. return;
  219. }
  220. if ( listDetails.available[href] === undefined ) {
  221. return;
  222. }
  223. listDetails.available[href].off = !this.checked;
  224. updateWidgets();
  225. };
  226. /******************************************************************************/
  227. var onListLinkClicked = function(ev) {
  228. messaging.tell({
  229. what: 'gotoExtensionURL',
  230. url: 'asset-viewer.html?url=' + uDom(this).attr('href')
  231. });
  232. ev.preventDefault();
  233. };
  234. /******************************************************************************/
  235. var onPurgeClicked = function() {
  236. var button = uDom(this);
  237. var li = button.parent();
  238. var href = li.descendants('a').first().attr('href');
  239. if ( !href ) {
  240. return;
  241. }
  242. messaging.tell({ what: 'purgeCache', path: href });
  243. button.remove();
  244. if ( li.descendants('input').first().prop('checked') ) {
  245. cacheWasPurged = true;
  246. updateWidgets();
  247. }
  248. };
  249. /******************************************************************************/
  250. var reloadAll = function(update) {
  251. // Loading may take a while when resources are fetched from remote
  252. // servers. We do not want the user to force reload while we are reloading.
  253. uDom('body').toggleClass('busy', true);
  254. // Reload blacklists
  255. var switches = [];
  256. var lis = uDom('#lists .listDetails');
  257. var i = lis.length;
  258. var path;
  259. while ( i-- ) {
  260. path = lis
  261. .subset(i, 1)
  262. .descendants('a')
  263. .attr('href');
  264. switches.push({
  265. location: path,
  266. off: lis.subset(i, 1).descendants('input').prop('checked') === false
  267. });
  268. }
  269. messaging.tell({
  270. what: 'reloadHostsFiles',
  271. switches: switches,
  272. update: update
  273. });
  274. cacheWasPurged = false;
  275. };
  276. /******************************************************************************/
  277. var buttonApplyHandler = function() {
  278. reloadAll(false);
  279. uDom('#buttonApply').toggleClass('enabled', false);
  280. };
  281. /******************************************************************************/
  282. var buttonUpdateHandler = function() {
  283. if ( needUpdate ) {
  284. reloadAll(true);
  285. }
  286. };
  287. /******************************************************************************/
  288. var buttonPurgeAllHandler = function() {
  289. var onCompleted = function() {
  290. renderBlacklists();
  291. };
  292. messaging.ask({ what: 'purgeAllCaches' }, onCompleted);
  293. };
  294. /******************************************************************************/
  295. var autoUpdateCheckboxChanged = function() {
  296. messaging.tell({
  297. what: 'userSettings',
  298. name: 'autoUpdate',
  299. value: this.checked
  300. });
  301. };
  302. /******************************************************************************/
  303. var renderExternalLists = function() {
  304. var onReceived = function(details) {
  305. uDom('#externalHostsFiles').val(details);
  306. externalHostsFiles = details;
  307. };
  308. messaging.ask({ what: 'userSettings', name: 'externalHostsFiles' }, onReceived);
  309. };
  310. /******************************************************************************/
  311. var externalListsChangeHandler = function() {
  312. uDom('#externalListsParse').prop(
  313. 'disabled',
  314. this.value.trim() === externalHostsFiles
  315. );
  316. };
  317. /******************************************************************************/
  318. var externalListsApplyHandler = function() {
  319. externalHostsFiles = uDom('#externalHostsFiles').val();
  320. messaging.tell({
  321. what: 'userSettings',
  322. name: 'externalHostsFiles',
  323. value: externalHostsFiles
  324. });
  325. renderBlacklists();
  326. uDom('#externalListsParse').prop('disabled', true);
  327. };
  328. /******************************************************************************/
  329. uDom.onLoad(function() {
  330. uDom('#autoUpdate').on('change', autoUpdateCheckboxChanged);
  331. uDom('#buttonApply').on('click', buttonApplyHandler);
  332. uDom('#buttonUpdate').on('click', buttonUpdateHandler);
  333. uDom('#buttonPurgeAll').on('click', buttonPurgeAllHandler);
  334. uDom('#lists').on('change', '.listDetails > input', onListCheckboxChanged);
  335. uDom('#lists').on('click', '.listDetails > a:nth-of-type(1)', onListLinkClicked);
  336. uDom('#lists').on('click', 'span.purge', onPurgeClicked);
  337. uDom('#externalHostsFiles').on('input', externalListsChangeHandler);
  338. uDom('#externalListsParse').on('click', externalListsApplyHandler);
  339. renderBlacklists();
  340. renderExternalLists();
  341. });
  342. /******************************************************************************/
  343. })();