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.

263 lines
8.8 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('[%s]', STORAGE_NAME, 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. // https://github.com/gorhill/uBlock/issues/3156
  91. // I have observed that no event was fired in Tor Browser 7.0.7 +
  92. // medium security level after the request to open the database was
  93. // created. When this occurs, I have also observed that the `error`
  94. // property was already set, so this means uBO can detect here whether
  95. // the database can be opened successfully. A try-catch block is
  96. // necessary when reading the `error` property because we are not
  97. // allowed to read this propery outside of event handlers in newer
  98. // implementation of IDBRequest (my understanding).
  99. var req;
  100. try {
  101. req = indexedDB.open(STORAGE_NAME, 1);
  102. if ( req.error ) {
  103. console.log(req.error);
  104. req = undefined;
  105. }
  106. } catch(ex) {
  107. }
  108. if ( req === undefined ) {
  109. processPendings();
  110. pending = undefined;
  111. return;
  112. }
  113. req.onupgradeneeded = function(ev) {
  114. req = undefined;
  115. db = ev.target.result;
  116. db.onerror = db.onabort = genericErrorHandler;
  117. var table = db.createObjectStore(STORAGE_NAME, { keyPath: 'key' });
  118. table.createIndex('value', 'value', { unique: false });
  119. };
  120. req.onsuccess = function(ev) {
  121. req = undefined;
  122. db = ev.target.result;
  123. db.onerror = db.onabort = genericErrorHandler;
  124. processPendings();
  125. };
  126. req.onerror = req.onblocked = function() {
  127. req = undefined;
  128. console.log(this.error);
  129. processPendings();
  130. pending = undefined;
  131. };
  132. }
  133. function getFromDb(keys, store, callback) {
  134. if ( typeof callback !== 'function' ) { return; }
  135. if ( keys.length === 0 ) { return callback(store); }
  136. var gotOne = function() {
  137. if ( typeof this.result === 'object' ) {
  138. store[this.result.key] = this.result.value;
  139. }
  140. };
  141. getDb(function(db) {
  142. if ( !db ) { return callback(); }
  143. var transaction = db.transaction(STORAGE_NAME);
  144. transaction.oncomplete =
  145. transaction.onerror =
  146. transaction.onabort = function() {
  147. return callback(store);
  148. };
  149. var table = transaction.objectStore(STORAGE_NAME);
  150. for ( var key of keys ) {
  151. var req = table.get(key);
  152. req.onsuccess = gotOne;
  153. req.onerror = noopfn;
  154. req = undefined;
  155. }
  156. });
  157. }
  158. function getAllFromDb(callback) {
  159. if ( typeof callback !== 'function' ) {
  160. callback = noopfn;
  161. }
  162. getDb(function(db) {
  163. if ( !db ) { return callback(); }
  164. var output = {};
  165. var transaction = db.transaction(STORAGE_NAME);
  166. transaction.oncomplete =
  167. transaction.onerror =
  168. transaction.onabort = function() {
  169. callback(output);
  170. };
  171. var table = transaction.objectStore(STORAGE_NAME),
  172. req = table.openCursor();
  173. req.onsuccess = function(ev) {
  174. var cursor = ev.target.result;
  175. if ( !cursor ) { return; }
  176. output[cursor.key] = cursor.value;
  177. cursor.continue();
  178. };
  179. });
  180. }
  181. function putToDb(input, callback) {
  182. if ( typeof callback !== 'function' ) {
  183. callback = noopfn;
  184. }
  185. var keys = Object.keys(input);
  186. if ( keys.length === 0 ) { return callback(); }
  187. getDb(function(db) {
  188. if ( !db ) { return callback(); }
  189. var transaction = db.transaction(STORAGE_NAME, 'readwrite');
  190. transaction.oncomplete =
  191. transaction.onerror =
  192. transaction.onabort = callback;
  193. var table = transaction.objectStore(STORAGE_NAME);
  194. for ( var key of keys ) {
  195. var entry = {};
  196. entry.key = key;
  197. entry.value = input[key];
  198. table.put(entry);
  199. entry = undefined;
  200. }
  201. });
  202. }
  203. function deleteFromDb(input, callback) {
  204. if ( typeof callback !== 'function' ) {
  205. callback = noopfn;
  206. }
  207. var keys = Array.isArray(input) ? input.slice() : [ input ];
  208. if ( keys.length === 0 ) { return callback(); }
  209. getDb(function(db) {
  210. if ( !db ) { return callback(); }
  211. var transaction = db.transaction(STORAGE_NAME, 'readwrite');
  212. transaction.oncomplete =
  213. transaction.onerror =
  214. transaction.onabort = callback;
  215. var table = transaction.objectStore(STORAGE_NAME);
  216. for ( var key of keys ) {
  217. table.delete(key);
  218. }
  219. });
  220. }
  221. function clearDb(callback) {
  222. if ( typeof callback !== 'function' ) {
  223. callback = noopfn;
  224. }
  225. getDb(function(db) {
  226. if ( !db ) { return callback(); }
  227. var req = db.transaction(STORAGE_NAME, 'readwrite')
  228. .objectStore(STORAGE_NAME)
  229. .clear();
  230. req.onsuccess = req.onerror = callback;
  231. });
  232. }
  233. }());
  234. /******************************************************************************/