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.

273 lines
8.7 KiB

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