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.

269 lines
9.0 KiB

  1. /*******************************************************************************
  2. uMatrix - a browser extension to block requests.
  3. Copyright (C) 2016-2017 The uBlock Origin authors
  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/uBlock
  15. */
  16. /* global indexedDB, IDBDatabase */
  17. 'use strict';
  18. /******************************************************************************/
  19. // The code below has been originally manually imported from:
  20. // Commit: https://github.com/nikrolls/uBlock-Edge/commit/d1538ea9bea89d507219d3219592382eee306134
  21. // Commit date: 29 October 2016
  22. // Commit author: https://github.com/nikrolls
  23. // Commit message: "Implement cacheStorage using IndexedDB"
  24. // The original imported code has been subsequently modified as it was not
  25. // compatible with Firefox.
  26. // (a Promise thing, see https://github.com/dfahlander/Dexie.js/issues/317)
  27. // Furthermore, code to migrate from browser.storage.local to vAPI.cacheStorage
  28. // has been added, for seamless migration of cache-related entries into
  29. // indexedDB.
  30. // Imported from uBlock Origin project.
  31. vAPI.cacheStorage = (function() {
  32. const STORAGE_NAME = 'uMatrixCacheStorage';
  33. var db;
  34. var pending = [];
  35. // prime the db so that it's ready asap for next access.
  36. getDb(noopfn);
  37. return { get, set, remove, clear, getBytesInUse };
  38. function get(input, callback) {
  39. if ( typeof callback !== 'function' ) { return; }
  40. if ( input === null ) {
  41. return getAllFromDb(callback);
  42. }
  43. var toRead, output = {};
  44. if ( typeof input === 'string' ) {
  45. toRead = [ input ];
  46. } else if ( Array.isArray(input) ) {
  47. toRead = input;
  48. } else /* if ( typeof input === 'object' ) */ {
  49. toRead = Object.keys(input);
  50. output = input;
  51. }
  52. return getFromDb(toRead, output, callback);
  53. }
  54. function set(input, callback) {
  55. putToDb(input, callback);
  56. }
  57. function remove(key, callback) {
  58. deleteFromDb(key, callback);
  59. }
  60. function clear(callback) {
  61. clearDb(callback);
  62. }
  63. function getBytesInUse(keys, callback) {
  64. // TODO: implement this
  65. callback(0);
  66. }
  67. function genericErrorHandler(error) {
  68. console.error('[uMatrix cacheStorage]', error);
  69. }
  70. function noopfn() {
  71. }
  72. function processPendings() {
  73. var cb;
  74. while ( (cb = pending.shift()) ) {
  75. cb(db);
  76. }
  77. }
  78. function getDb(callback) {
  79. if ( pending === undefined ) {
  80. return callback();
  81. }
  82. if ( pending.length !== 0 ) {
  83. return pending.push(callback);
  84. }
  85. if ( db instanceof IDBDatabase ) {
  86. return callback(db);
  87. }
  88. pending.push(callback);
  89. if ( pending.length !== 1 ) { return; }
  90. // This will fail in private browsing mode.
  91. var req = indexedDB.open(STORAGE_NAME, 1);
  92. req.onupgradeneeded = function(ev) {
  93. db = ev.target.result;
  94. db.onerror = genericErrorHandler;
  95. var table = db.createObjectStore(STORAGE_NAME, { keyPath: 'key' });
  96. table.createIndex('value', 'value', { unique: false });
  97. };
  98. req.onsuccess = function(ev) {
  99. db = ev.target.result;
  100. db.onerror = genericErrorHandler;
  101. processPendings();
  102. };
  103. req.onerror = function() {
  104. console.log(this.error);
  105. processPendings();
  106. pending = undefined;
  107. };
  108. }
  109. function getFromDb(keys, store, callback) {
  110. if ( typeof callback !== 'function' ) { return; }
  111. if ( keys.length === 0 ) { return callback(store); }
  112. var notfoundKeys = new Set(keys);
  113. var gotOne = function() {
  114. if ( typeof this.result === 'object' ) {
  115. store[this.result.key] = this.result.value;
  116. notfoundKeys.delete(this.result.key);
  117. }
  118. };
  119. getDb(function(db) {
  120. if ( !db ) { return callback(); }
  121. var transaction = db.transaction(STORAGE_NAME);
  122. transaction.oncomplete = transaction.onerror = function() {
  123. if ( notfoundKeys.size === 0 ) {
  124. return callback(store);
  125. }
  126. maybeMigrate(Array.from(notfoundKeys), store, callback);
  127. };
  128. var table = transaction.objectStore(STORAGE_NAME);
  129. for ( var key of keys ) {
  130. var req = table.get(key);
  131. req.onsuccess = gotOne;
  132. req.onerror = noopfn;
  133. }
  134. });
  135. }
  136. // Migrate from storage API
  137. // TODO: removes once all users are migrated to the new cacheStorage.
  138. function maybeMigrate(keys, store, callback) {
  139. var toMigrate = new Set(),
  140. i = keys.length;
  141. while ( i-- ) {
  142. var key = keys[i];
  143. toMigrate.add(key);
  144. // If migrating a compiled list, also migrate the non-compiled
  145. // counterpart.
  146. if ( /^cache\/compiled\//.test(key) ) {
  147. toMigrate.add(key.replace('/compiled', ''));
  148. }
  149. }
  150. vAPI.storage.get(Array.from(toMigrate), function(bin) {
  151. if ( bin instanceof Object === false ) {
  152. return callback(store);
  153. }
  154. var migratedKeys = Object.keys(bin);
  155. if ( migratedKeys.length === 0 ) {
  156. return callback(store);
  157. }
  158. var i = migratedKeys.length;
  159. while ( i-- ) {
  160. var key = migratedKeys[i];
  161. store[key] = bin[key];
  162. }
  163. vAPI.storage.remove(migratedKeys);
  164. vAPI.cacheStorage.set(bin);
  165. callback(store);
  166. });
  167. }
  168. function getAllFromDb(callback) {
  169. if ( typeof callback !== 'function' ) {
  170. callback = noopfn;
  171. }
  172. getDb(function(db) {
  173. if ( !db ) { return callback(); }
  174. var output = {};
  175. var transaction = db.transaction(STORAGE_NAME);
  176. transaction.oncomplete = transaction.onerror = function() {
  177. callback(output);
  178. };
  179. var table = transaction.objectStore(STORAGE_NAME),
  180. req = table.openCursor();
  181. req.onsuccess = function(ev) {
  182. var cursor = ev.target.result;
  183. if ( !cursor ) { return; }
  184. output[cursor.key] = cursor.value;
  185. cursor.continue();
  186. };
  187. });
  188. }
  189. function putToDb(input, callback) {
  190. if ( typeof callback !== 'function' ) {
  191. callback = noopfn;
  192. }
  193. var keys = Object.keys(input);
  194. if ( keys.length === 0 ) { return callback(); }
  195. getDb(function(db) {
  196. if ( !db ) { return callback(); }
  197. var transaction = db.transaction(STORAGE_NAME, 'readwrite');
  198. transaction.oncomplete = transaction.onerror = callback;
  199. var table = transaction.objectStore(STORAGE_NAME),
  200. entry = {};
  201. for ( var key of keys ) {
  202. entry.key = key;
  203. entry.value = input[key];
  204. table.put(entry);
  205. }
  206. });
  207. }
  208. function deleteFromDb(input, callback) {
  209. if ( typeof callback !== 'function' ) {
  210. callback = noopfn;
  211. }
  212. var keys = Array.isArray(input) ? input.slice() : [ input ];
  213. if ( keys.length === 0 ) { return callback(); }
  214. getDb(function(db) {
  215. if ( !db ) { return callback(); }
  216. var transaction = db.transaction(STORAGE_NAME, 'readwrite');
  217. transaction.oncomplete = transaction.onerror = callback;
  218. var table = transaction.objectStore(STORAGE_NAME);
  219. for ( var key of keys ) {
  220. table.delete(key);
  221. }
  222. });
  223. // TODO: removes once all users are migrated to the new cacheStorage.
  224. vAPI.storage.remove(keys);
  225. }
  226. function clearDb(callback) {
  227. if ( typeof callback !== 'function' ) {
  228. callback = noopfn;
  229. }
  230. getDb(function(db) {
  231. if ( !db ) { return callback(); }
  232. var req = db.transaction(STORAGE_NAME, 'readwrite')
  233. .objectStore(STORAGE_NAME)
  234. .clear();
  235. req.onsuccess = req.onerror = callback;
  236. });
  237. }
  238. }());
  239. /******************************************************************************/