pagestats.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /*******************************************************************************
  2. ηMatrix - a browser extension to black/white list requests.
  3. Copyright (C) 2013-2019 Raymond Hill
  4. Copyright (C) 2019-2022 Alessio Vanni
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see {http://www.gnu.org/licenses/}.
  15. Home: https://gitlab.com/vannilla/ematrix
  16. uMatrix Home: https://github.com/gorhill/uMatrix
  17. */
  18. 'use strict';
  19. ηMatrix.pageStoreFactory = (function() {
  20. Cu.import('chrome://ematrix/content/lib/UriTools.jsm');
  21. let ηm = ηMatrix;
  22. let BlockedCollapsibles = function () {
  23. this.boundPruneAsyncCallback = this.pruneAsyncCallback.bind(this);
  24. this.blocked = new Map();
  25. this.hash = 0;
  26. this.timer = null;
  27. };
  28. BlockedCollapsibles.prototype = {
  29. shelfLife: 10 * 1000,
  30. add: function (type, url, isSpecific) {
  31. if (this.blocked.size === 0) {
  32. this.pruneAsync();
  33. }
  34. let now = Date.now() / 1000 | 0;
  35. // The following "trick" is to encode the specifity into
  36. // the lsb of the time stamp so as to avoid to have to
  37. // allocate a memory structure to store both time stamp
  38. // and specificity.
  39. if (isSpecific) {
  40. now |= 0x00000001;
  41. } else {
  42. now &= 0xFFFFFFFE;
  43. }
  44. this.blocked.set(type + ' ' + url, now);
  45. this.hash = now;
  46. },
  47. reset: function () {
  48. this.blocked.clear();
  49. this.hash = 0;
  50. if (this.timer !== null) {
  51. clearTimeout(this.timer);
  52. this.timer = null;
  53. }
  54. },
  55. pruneAsync: function () {
  56. if (this.timer === null) {
  57. this.timer = vAPI.setTimeout(this.boundPruneAsyncCallback,
  58. this.shelfLife * 2);
  59. }
  60. },
  61. pruneAsyncCallback: function () {
  62. this.timer = null;
  63. let obsolete = Date.now() - this.shelfLife;
  64. for (let entry of this.blocked) {
  65. if (entry[1] <= obsolete) {
  66. this.blocked.delete(entry[0]);
  67. }
  68. }
  69. if (this.blocked.size !== 0) {
  70. this.pruneAsync();
  71. }
  72. }
  73. };
  74. // Ref: Given a URL, returns a (somewhat) unique 32-bit value
  75. // Based on: FNV32a
  76. // http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-reference-source
  77. // The rest is custom, suited for uMatrix.
  78. let PageStore = function (tabContext) {
  79. this.hostnameTypeCells = new Map();
  80. this.domains = new Set();
  81. this.blockedCollapsibles = new BlockedCollapsibles();
  82. this.requestStats = ηm.requestStatsFactory();
  83. this.off = false;
  84. this.init(tabContext);
  85. };
  86. PageStore.prototype = {
  87. collapsibleTypes: new Set([ 'image' ]),
  88. pageStoreJunkyard: [],
  89. init: function (tabContext) {
  90. this.tabId = tabContext.tabId;
  91. this.rawUrl = tabContext.rawURL;
  92. this.pageUrl = tabContext.normalURL;
  93. this.pageHostname = tabContext.rootHostname;
  94. this.pageDomain = tabContext.rootDomain;
  95. this.title = '';
  96. this.hostnameTypeCells.clear();
  97. this.domains.clear();
  98. this.allHostnamesString = ' ';
  99. this.blockedCollapsibles.reset();
  100. this.requestStats.reset();
  101. this.distinctRequestCount = 0;
  102. this.perLoadAllowedRequestCount = 0;
  103. this.perLoadBlockedRequestCount = 0;
  104. this.has3pReferrer = false;
  105. this.hasMixedContent = false;
  106. this.hasNoscriptTags = false;
  107. this.hasWebWorkers = false;
  108. this.incinerationTimer = null;
  109. this.mtxContentModifiedTime = 0;
  110. this.mtxCountModifiedTime = 0;
  111. return this;
  112. },
  113. dispose: function () {
  114. this.rawUrl = '';
  115. this.pageUrl = '';
  116. this.pageHostname = '';
  117. this.pageDomain = '';
  118. this.title = '';
  119. this.hostnameTypeCells.clear();
  120. this.domains.clear();
  121. this.allHostnamesString = ' ';
  122. this.blockedCollapsibles.reset();
  123. if (this.incinerationTimer !== null) {
  124. clearTimeout(this.incinerationTimer);
  125. this.incinerationTimer = null;
  126. }
  127. if (this.pageStoreJunkyard.length < 8) {
  128. this.pageStoreJunkyard.push(this);
  129. }
  130. },
  131. cacheBlockedCollapsible: function (type, url, specificity) {
  132. if (this.collapsibleTypes.has(type)) {
  133. this.blockedCollapsibles.add(type,
  134. url,
  135. specificity !== 0
  136. && specificity < 5);
  137. }
  138. },
  139. lookupBlockedCollapsibles: function (request, response) {
  140. let tabContext = ηm.tabContextManager.lookup(this.tabId);
  141. if (tabContext === null) {
  142. return;
  143. }
  144. let collapseBlacklisted = ηm.userSettings.collapseBlacklisted;
  145. let collapseBlocked = ηm.userSettings.collapseBlocked;
  146. let blockedResources = response.blockedResources;
  147. if (Array.isArray(request.toFilter) && request.toFilter.length !== 0) {
  148. let roothn = tabContext.rootHostname;
  149. let hnFromURI = UriTools.hostnameFromURI;
  150. let tMatrix = ηm.tMatrix;
  151. for (let entry of request.toFilter) {
  152. if (tMatrix.mustBlock(roothn,
  153. hnFromURI(entry.url),
  154. entry.type) === false) {
  155. continue;
  156. }
  157. blockedResources.push([
  158. entry.type + ' ' + entry.url,
  159. collapseBlocked
  160. || collapseBlacklisted
  161. && tMatrix.specificityRegister !== 0
  162. && tMatrix.specificityRegister < 5
  163. ]);
  164. }
  165. }
  166. if (this.blockedCollapsibles.hash === response.hash) {
  167. return;
  168. }
  169. response.hash = this.blockedCollapsibles.hash;
  170. for (let entry of this.blockedCollapsibles.blocked) {
  171. blockedResources.push([
  172. entry[0],
  173. collapseBlocked
  174. || collapseBlacklisted
  175. && (entry[1] & 1) !== 0
  176. ]);
  177. }
  178. },
  179. recordRequest: function (type, url, block) {
  180. if (block !== false) {
  181. this.perLoadBlockedRequestCount++;
  182. } else {
  183. this.perLoadAllowedRequestCount++;
  184. }
  185. // Store distinct network requests. This is used to:
  186. // - remember which hostname/type were seen
  187. // - count the number of distinct URLs for any given
  188. // hostname-type pair
  189. let hostname = UriTools.hostnameFromURI(url);
  190. let key = hostname + ' ' + type;
  191. let uids = this.hostnameTypeCells.get(key);
  192. if (uids === undefined) {
  193. this.hostnameTypeCells.set(key, (uids = new Set()));
  194. } else if (uids.size > 99) {
  195. return;
  196. }
  197. let uid = this.uidFromURL(url);
  198. if (uids.has(uid)) {
  199. return;
  200. }
  201. uids.add(uid);
  202. // Count blocked/allowed requests
  203. this.requestStats.record(type, block);
  204. // https://github.com/gorhill/httpswitchboard/issues/306
  205. // If it is recorded locally, record globally
  206. ηm.requestStats.record(type, block);
  207. ηm.updateBadgeAsync(this.tabId);
  208. // this.distinctRequestCount++;
  209. this.mtxCountModifiedTime = Date.now();
  210. if (this.domains.has(hostname) === false) {
  211. this.domains.add(hostname);
  212. this.allHostnamesString += hostname + ' ';
  213. this.mtxContentModifiedTime = Date.now();
  214. }
  215. },
  216. uidFromURL: function (uri) {
  217. var hint = 0x811c9dc5;
  218. let i = uri.length;
  219. while (i--) {
  220. hint ^= uri.charCodeAt(i) | 0;
  221. hint += (hint<<1) + (hint<<4) + (hint<<7) + (hint<<8) + (hint<<24) | 0;
  222. hint >>>= 0;
  223. }
  224. return hint;
  225. }
  226. };
  227. return function (tabContext) {
  228. let entry = PageStore.prototype.pageStoreJunkyard.pop();
  229. if (entry) {
  230. return entry.init(tabContext);
  231. }
  232. return new PageStore(tabContext);
  233. };
  234. })();