uBlock/platform/webext/vapi-cachestorage.js

262 lines
8.7 KiB
JavaScript
Raw Normal View History

/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2016-2017 The uBlock Origin authors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
2017-08-29 16:32:00 -06:00
/* global indexedDB, IDBDatabase */
'use strict';
/******************************************************************************/
// The code below has been originally manually imported from:
// Commit: https://github.com/nikrolls/uBlock-Edge/commit/d1538ea9bea89d507219d3219592382eee306134
// Commit date: 29 October 2016
// Commit author: https://github.com/nikrolls
// Commit message: "Implement cacheStorage using IndexedDB"
2017-08-29 16:32:00 -06:00
// The original imported code has been subsequently modified as it was not
// compatible with Firefox.
// (a Promise thing, see https://github.com/dfahlander/Dexie.js/issues/317)
// Furthermore, code to migrate from browser.storage.local to vAPI.cacheStorage
// has been added, for seamless migration of cache-related entries into
// indexedDB.
vAPI.cacheStorage = (function() {
2017-08-29 16:32:00 -06:00
const STORAGE_NAME = 'uBlock0CacheStorage';
var db;
var pending = [];
2017-08-29 16:32:00 -06:00
// prime the db so that it's ready asap for next access.
getDb(noopfn);
2017-08-29 16:32:00 -06:00
return { get, set, remove, clear, getBytesInUse };
2017-08-29 16:32:00 -06:00
function get(input, callback) {
if ( typeof callback !== 'function' ) { return; }
if ( input === null ) {
return getAllFromDb(callback);
}
var toRead, output = {};
if ( typeof input === 'string' ) {
toRead = [ input ];
} else if ( Array.isArray(input) ) {
toRead = input;
} else /* if ( typeof input === 'object' ) */ {
toRead = Object.keys(input);
output = input;
}
return getFromDb(toRead, output, callback);
}
2017-08-29 16:32:00 -06:00
function set(input, callback) {
putToDb(input, callback);
}
2017-08-29 16:32:00 -06:00
function remove(key, callback) {
deleteFromDb(key, callback);
}
function clear(callback) {
2017-08-29 16:32:00 -06:00
clearDb(callback);
}
function getBytesInUse(keys, callback) {
// TODO: implement this
callback(0);
}
2017-08-29 16:32:00 -06:00
function genericErrorHandler(error) {
2017-11-30 06:42:31 -07:00
console.error('[%s]', STORAGE_NAME, error);
}
2017-08-29 16:32:00 -06:00
function noopfn() {
}
function processPendings() {
var cb;
while ( (cb = pending.shift()) ) {
cb(db);
}
}
2017-08-29 16:32:00 -06:00
function getDb(callback) {
if ( pending === undefined ) {
return callback();
}
2017-08-29 16:32:00 -06:00
if ( pending.length !== 0 ) {
return pending.push(callback);
2017-08-29 16:32:00 -06:00
}
if ( db instanceof IDBDatabase ) {
return callback(db);
}
pending.push(callback);
if ( pending.length !== 1 ) { return; }
2017-10-21 06:43:45 -06:00
// https://github.com/gorhill/uBlock/issues/3156
// I have observed that no event was fired in Tor Browser 7.0.7 +
// medium security level after the request to open the database was
// created. When this occurs, I have also observed that the `error`
// property was already set, so this means uBO can detect here whether
// the database can be opened successfully. A try-catch block is
// necessary when reading the `error` property because we are not
// allowed to read this propery outside of event handlers in newer
// implementation of IDBRequest (my understanding).
var req;
2017-10-21 06:43:45 -06:00
try {
req = indexedDB.open(STORAGE_NAME, 1);
2017-10-21 06:43:45 -06:00
if ( req.error ) {
console.log(req.error);
req = undefined;
2017-10-21 06:43:45 -06:00
}
} catch(ex) {
}
if ( req === undefined ) {
processPendings();
pending = undefined;
return;
}
2017-08-29 16:32:00 -06:00
req.onupgradeneeded = function(ev) {
2017-10-21 06:43:45 -06:00
req = undefined;
2017-08-29 16:32:00 -06:00
db = ev.target.result;
db.onerror = db.onabort = genericErrorHandler;
2017-08-29 16:32:00 -06:00
var table = db.createObjectStore(STORAGE_NAME, { keyPath: 'key' });
table.createIndex('value', 'value', { unique: false });
};
req.onsuccess = function(ev) {
2017-10-21 06:43:45 -06:00
req = undefined;
2017-08-29 16:32:00 -06:00
db = ev.target.result;
db.onerror = db.onabort = genericErrorHandler;
processPendings();
2017-08-29 16:32:00 -06:00
};
2017-10-21 06:43:45 -06:00
req.onerror = req.onblocked = function() {
req = undefined;
console.log(this.error);
processPendings();
pending = undefined;
2017-08-29 16:32:00 -06:00
};
}
2017-08-29 16:32:00 -06:00
function getFromDb(keys, store, callback) {
if ( typeof callback !== 'function' ) { return; }
if ( keys.length === 0 ) { return callback(store); }
var gotOne = function() {
if ( typeof this.result === 'object' ) {
store[this.result.key] = this.result.value;
}
};
getDb(function(db) {
if ( !db ) { return callback(); }
var transaction = db.transaction(STORAGE_NAME);
transaction.oncomplete =
transaction.onerror =
transaction.onabort = function() {
return callback(store);
2017-08-29 16:32:00 -06:00
};
var table = transaction.objectStore(STORAGE_NAME);
for ( var key of keys ) {
var req = table.get(key);
req.onsuccess = gotOne;
req.onerror = noopfn;
req = undefined;
2017-08-29 16:32:00 -06:00
}
});
}
2017-08-29 16:32:00 -06:00
function getAllFromDb(callback) {
if ( typeof callback !== 'function' ) {
callback = noopfn;
}
getDb(function(db) {
if ( !db ) { return callback(); }
var output = {};
var transaction = db.transaction(STORAGE_NAME);
transaction.oncomplete =
transaction.onerror =
transaction.onabort = function() {
2017-08-29 16:32:00 -06:00
callback(output);
};
var table = transaction.objectStore(STORAGE_NAME),
req = table.openCursor();
req.onsuccess = function(ev) {
var cursor = ev.target.result;
if ( !cursor ) { return; }
output[cursor.key] = cursor.value;
cursor.continue();
};
});
}
2017-08-29 16:32:00 -06:00
function putToDb(input, callback) {
if ( typeof callback !== 'function' ) {
callback = noopfn;
}
var keys = Object.keys(input);
if ( keys.length === 0 ) { return callback(); }
getDb(function(db) {
if ( !db ) { return callback(); }
var transaction = db.transaction(STORAGE_NAME, 'readwrite');
transaction.oncomplete =
transaction.onerror =
transaction.onabort = callback;
var table = transaction.objectStore(STORAGE_NAME);
2017-08-29 16:32:00 -06:00
for ( var key of keys ) {
var entry = {};
2017-08-29 16:32:00 -06:00
entry.key = key;
entry.value = input[key];
table.put(entry);
entry = undefined;
2017-08-29 16:32:00 -06:00
}
});
}
2017-08-29 16:32:00 -06:00
function deleteFromDb(input, callback) {
if ( typeof callback !== 'function' ) {
callback = noopfn;
}
var keys = Array.isArray(input) ? input.slice() : [ input ];
if ( keys.length === 0 ) { return callback(); }
getDb(function(db) {
if ( !db ) { return callback(); }
var transaction = db.transaction(STORAGE_NAME, 'readwrite');
transaction.oncomplete =
transaction.onerror =
transaction.onabort = callback;
2017-08-29 16:32:00 -06:00
var table = transaction.objectStore(STORAGE_NAME);
for ( var key of keys ) {
table.delete(key);
}
});
}
2017-08-29 16:32:00 -06:00
function clearDb(callback) {
if ( typeof callback !== 'function' ) {
callback = noopfn;
}
getDb(function(db) {
if ( !db ) { return callback(); }
var req = db.transaction(STORAGE_NAME, 'readwrite')
.objectStore(STORAGE_NAME)
.clear();
req.onsuccess = req.onerror = callback;
});
}
}());
/******************************************************************************/