WIP
This commit is contained in:
parent
24b1eea8a4
commit
0f148eac32
134
background/db.js
Normal file
134
background/db.js
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
const db = (() => {
|
||||||
|
let exec;
|
||||||
|
const preparing = prepare();
|
||||||
|
return {
|
||||||
|
exec: (...args) =>
|
||||||
|
preparing.then(() => exec(...args))
|
||||||
|
};
|
||||||
|
|
||||||
|
function prepare() {
|
||||||
|
// we use chrome.storage.local fallback if IndexedDB doesn't save data,
|
||||||
|
// which, once detected on the first run, is remembered in chrome.storage.local
|
||||||
|
// for reliablility and in localStorage for fast synchronous access
|
||||||
|
// (FF may block localStorage depending on its privacy options)
|
||||||
|
|
||||||
|
// test localStorage
|
||||||
|
const fallbackSet = localStorage.dbInChromeStorage;
|
||||||
|
if (fallbackSet === 'true' || !tryCatch(() => indexedDB)) {
|
||||||
|
useChromeStorage();
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
if (fallbackSet === 'false') {
|
||||||
|
useIndexedDB();
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
// test storage.local
|
||||||
|
return chromeLocal.get('dbInChromeStorage')
|
||||||
|
.then(data =>
|
||||||
|
data && data.dbInChromeStorage && Promise.reject())
|
||||||
|
.then(() =>
|
||||||
|
tryCatch(dbExecIndexedDB, 'getAllKeys', IDBKeyRange.lowerBound(1), 1) ||
|
||||||
|
Promise.reject())
|
||||||
|
.then(({target}) => (
|
||||||
|
(target.result || [])[0] ?
|
||||||
|
Promise.reject('ok') :
|
||||||
|
dbExecIndexedDB('put', {id: -1})))
|
||||||
|
.then(() =>
|
||||||
|
dbExecIndexedDB('get', -1))
|
||||||
|
.then(({target}) => (
|
||||||
|
(target.result || {}).id === -1 ?
|
||||||
|
dbExecIndexedDB('delete', -1) :
|
||||||
|
Promise.reject()))
|
||||||
|
.then(() =>
|
||||||
|
Promise.reject('ok'))
|
||||||
|
.catch(result => {
|
||||||
|
if (result === 'ok') {
|
||||||
|
useIndexedDB();
|
||||||
|
} else {
|
||||||
|
useChromeStorage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function useChromeStorage() {
|
||||||
|
exec = dbExecChromeStorage;
|
||||||
|
chromeLocal.set({dbInChromeStorage: true}, ignoreChromeError);
|
||||||
|
localStorage.dbInChromeStorage = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
function useIndexedDB() {
|
||||||
|
exec = dbExecIndexedDB;
|
||||||
|
chromeLocal.set({dbInChromeStorage: false}, ignoreChromeError);
|
||||||
|
localStorage.dbInChromeStorage = 'false';
|
||||||
|
}
|
||||||
|
|
||||||
|
function dbExecIndexedDB(method, ...args) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
Object.assign(indexedDB.open('stylish', 2), {
|
||||||
|
onsuccess(event) {
|
||||||
|
const database = event.target.result;
|
||||||
|
if (!method) {
|
||||||
|
resolve(database);
|
||||||
|
} else {
|
||||||
|
const transaction = database.transaction(['styles'], 'readwrite');
|
||||||
|
const store = transaction.objectStore('styles');
|
||||||
|
Object.assign(store[method](...args), {
|
||||||
|
onsuccess: event => resolve(event, store, transaction, database),
|
||||||
|
onerror: reject,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onerror(event) {
|
||||||
|
console.warn(event.target.error || event.target.errorCode);
|
||||||
|
reject(event);
|
||||||
|
},
|
||||||
|
onupgradeneeded(event) {
|
||||||
|
if (event.oldVersion === 0) {
|
||||||
|
event.target.result.createObjectStore('styles', {
|
||||||
|
keyPath: 'id',
|
||||||
|
autoIncrement: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function dbExecChromeStorage(method, data) {
|
||||||
|
const STYLE_KEY_PREFIX = 'style-';
|
||||||
|
switch (method) {
|
||||||
|
case 'get':
|
||||||
|
return chromeLocal.getValue(STYLE_KEY_PREFIX + data)
|
||||||
|
.then(result => ({target: {result}}));
|
||||||
|
|
||||||
|
case 'put':
|
||||||
|
if (!data.id) {
|
||||||
|
return getStyles().then(() => {
|
||||||
|
data.id = 1;
|
||||||
|
for (const style of cachedStyles.list) {
|
||||||
|
data.id = Math.max(data.id, style.id + 1);
|
||||||
|
}
|
||||||
|
return dbExecChromeStorage('put', data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return chromeLocal.setValue(STYLE_KEY_PREFIX + data.id, data)
|
||||||
|
.then(() => (chrome.runtime.lastError ? Promise.reject() : data.id));
|
||||||
|
|
||||||
|
case 'delete':
|
||||||
|
return chromeLocal.remove(STYLE_KEY_PREFIX + data);
|
||||||
|
|
||||||
|
case 'getAll':
|
||||||
|
return chromeLocal.get(null).then(storage => {
|
||||||
|
const styles = [];
|
||||||
|
for (const key in storage) {
|
||||||
|
if (key.startsWith(STYLE_KEY_PREFIX) &&
|
||||||
|
Number(key.substr(STYLE_KEY_PREFIX.length))) {
|
||||||
|
styles.push(storage[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {target: {result: styles}};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
})();
|
|
@ -29,139 +29,6 @@ var cachedStyles = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
|
||||||
var dbExec = dbExecIndexedDB;
|
|
||||||
dbExec.initialized = false;
|
|
||||||
|
|
||||||
// we use chrome.storage.local fallback if IndexedDB doesn't save data,
|
|
||||||
// which, once detected on the first run, is remembered in chrome.storage.local
|
|
||||||
// for reliablility and in localStorage for fast synchronous access
|
|
||||||
// (FF may block localStorage depending on its privacy options)
|
|
||||||
do {
|
|
||||||
const done = () => {
|
|
||||||
cachedStyles.mutex.inProgress = false;
|
|
||||||
getStyles().then(() => {
|
|
||||||
dbExec.initialized = true;
|
|
||||||
window.dispatchEvent(new Event('storageReady'));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const fallback = () => {
|
|
||||||
dbExec = dbExecChromeStorage;
|
|
||||||
chromeLocal.set({dbInChromeStorage: true});
|
|
||||||
localStorage.dbInChromeStorage = 'true';
|
|
||||||
ignoreChromeError();
|
|
||||||
done();
|
|
||||||
};
|
|
||||||
const fallbackSet = localStorage.dbInChromeStorage;
|
|
||||||
if (fallbackSet === 'true' || !tryCatch(() => indexedDB)) {
|
|
||||||
fallback();
|
|
||||||
break;
|
|
||||||
} else if (fallbackSet === 'false') {
|
|
||||||
done();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
chromeLocal.get('dbInChromeStorage')
|
|
||||||
.then(data =>
|
|
||||||
data && data.dbInChromeStorage && Promise.reject())
|
|
||||||
.then(() =>
|
|
||||||
tryCatch(dbExecIndexedDB, 'getAllKeys', IDBKeyRange.lowerBound(1), 1) ||
|
|
||||||
Promise.reject())
|
|
||||||
.then(({target}) => (
|
|
||||||
(target.result || [])[0] ?
|
|
||||||
Promise.reject('ok') :
|
|
||||||
dbExecIndexedDB('put', {id: -1})))
|
|
||||||
.then(() =>
|
|
||||||
dbExecIndexedDB('get', -1))
|
|
||||||
.then(({target}) => (
|
|
||||||
(target.result || {}).id === -1 ?
|
|
||||||
dbExecIndexedDB('delete', -1) :
|
|
||||||
Promise.reject()))
|
|
||||||
.then(() =>
|
|
||||||
Promise.reject('ok'))
|
|
||||||
.catch(result => {
|
|
||||||
if (result === 'ok') {
|
|
||||||
chromeLocal.set({dbInChromeStorage: false});
|
|
||||||
localStorage.dbInChromeStorage = 'false';
|
|
||||||
done();
|
|
||||||
} else {
|
|
||||||
fallback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} while (0);
|
|
||||||
|
|
||||||
|
|
||||||
function dbExecIndexedDB(method, ...args) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
Object.assign(indexedDB.open('stylish', 2), {
|
|
||||||
onsuccess(event) {
|
|
||||||
const database = event.target.result;
|
|
||||||
if (!method) {
|
|
||||||
resolve(database);
|
|
||||||
} else {
|
|
||||||
const transaction = database.transaction(['styles'], 'readwrite');
|
|
||||||
const store = transaction.objectStore('styles');
|
|
||||||
Object.assign(store[method](...args), {
|
|
||||||
onsuccess: event => resolve(event, store, transaction, database),
|
|
||||||
onerror: reject,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onerror(event) {
|
|
||||||
console.warn(event.target.error || event.target.errorCode);
|
|
||||||
reject(event);
|
|
||||||
},
|
|
||||||
onupgradeneeded(event) {
|
|
||||||
if (event.oldVersion === 0) {
|
|
||||||
event.target.result.createObjectStore('styles', {
|
|
||||||
keyPath: 'id',
|
|
||||||
autoIncrement: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function dbExecChromeStorage(method, data) {
|
|
||||||
const STYLE_KEY_PREFIX = 'style-';
|
|
||||||
switch (method) {
|
|
||||||
case 'get':
|
|
||||||
return chromeLocal.getValue(STYLE_KEY_PREFIX + data)
|
|
||||||
.then(result => ({target: {result}}));
|
|
||||||
|
|
||||||
case 'put':
|
|
||||||
if (!data.id) {
|
|
||||||
return getStyles().then(() => {
|
|
||||||
data.id = 1;
|
|
||||||
for (const style of cachedStyles.list) {
|
|
||||||
data.id = Math.max(data.id, style.id + 1);
|
|
||||||
}
|
|
||||||
return dbExecChromeStorage('put', data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return chromeLocal.setValue(STYLE_KEY_PREFIX + data.id, data)
|
|
||||||
.then(() => (chrome.runtime.lastError ? Promise.reject() : data.id));
|
|
||||||
|
|
||||||
case 'delete':
|
|
||||||
return chromeLocal.remove(STYLE_KEY_PREFIX + data);
|
|
||||||
|
|
||||||
case 'getAll':
|
|
||||||
return chromeLocal.get(null).then(storage => {
|
|
||||||
const styles = [];
|
|
||||||
for (const key in storage) {
|
|
||||||
if (key.startsWith(STYLE_KEY_PREFIX) &&
|
|
||||||
Number(key.substr(STYLE_KEY_PREFIX.length))) {
|
|
||||||
styles.push(storage[key]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {target: {result: styles}};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Promise.reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getStyles(options) {
|
function getStyles(options) {
|
||||||
if (cachedStyles.list) {
|
if (cachedStyles.list) {
|
||||||
return Promise.resolve(filterStyles(options));
|
return Promise.resolve(filterStyles(options));
|
||||||
|
@ -172,24 +39,6 @@ function getStyles(options) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
cachedStyles.mutex.inProgress = true;
|
cachedStyles.mutex.inProgress = true;
|
||||||
|
|
||||||
return dbExec('getAll').then(event => {
|
|
||||||
cachedStyles.list = event.target.result || [];
|
|
||||||
cachedStyles.byId.clear();
|
|
||||||
for (const style of cachedStyles.list) {
|
|
||||||
cachedStyles.byId.set(style.id, style);
|
|
||||||
if (!style.name) {
|
|
||||||
style.name = 'ID: ' + style.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cachedStyles.mutex.inProgress = false;
|
|
||||||
for (const {options, resolve} of cachedStyles.mutex.onDone) {
|
|
||||||
resolve(filterStyles(options));
|
|
||||||
}
|
|
||||||
cachedStyles.mutex.onDone = [];
|
|
||||||
return filterStyles(options);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -202,29 +51,10 @@ function filterStyles({
|
||||||
omitCode,
|
omitCode,
|
||||||
strictRegexp = true, // used by the popup to detect bad regexps
|
strictRegexp = true, // used by the popup to detect bad regexps
|
||||||
} = {}) {
|
} = {}) {
|
||||||
if (id) id = Number(id);
|
|
||||||
if (asHash) enabled = true;
|
|
||||||
|
|
||||||
if (
|
|
||||||
enabled === null &&
|
|
||||||
id === null &&
|
|
||||||
matchUrl === null &&
|
|
||||||
md5Url === null &&
|
|
||||||
asHash !== true
|
|
||||||
) {
|
|
||||||
return cachedStyles.list;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchUrl && !URLS.supported(matchUrl)) {
|
if (matchUrl && !URLS.supported(matchUrl)) {
|
||||||
return asHash ? {length: 0} : [];
|
return asHash ? {length: 0} : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const blankHash = asHash && {
|
|
||||||
length: 0,
|
|
||||||
disableAll: prefs.get('disableAll'),
|
|
||||||
exposeIframes: prefs.get('exposeIframes'),
|
|
||||||
};
|
|
||||||
|
|
||||||
// make sure to use the same order in updateFiltersCache()
|
// make sure to use the same order in updateFiltersCache()
|
||||||
const cacheKey =
|
const cacheKey =
|
||||||
enabled + '\t' +
|
enabled + '\t' +
|
||||||
|
@ -287,14 +117,6 @@ function filterStylesInternal({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = id === null
|
|
||||||
? cachedStyles.list
|
|
||||||
: [cachedStyles.byId.get(id)];
|
|
||||||
if (!styles[0]) {
|
|
||||||
// may happen when users [accidentally] reopen an old URL
|
|
||||||
// of edit.html with a non-existent style id parameter
|
|
||||||
return asHash ? blankHash : [];
|
|
||||||
}
|
|
||||||
const filtered = asHash ? {length: 0} : [];
|
const filtered = asHash ? {length: 0} : [];
|
||||||
const needSections = asHash || matchUrl !== null;
|
const needSections = asHash || matchUrl !== null;
|
||||||
const matchUrlBase = matchUrl && matchUrl.includes('#') && matchUrl.split('#', 1)[0];
|
const matchUrlBase = matchUrl && matchUrl.includes('#') && matchUrl.split('#', 1)[0];
|
||||||
|
@ -342,97 +164,6 @@ function filterStylesInternal({
|
||||||
|
|
||||||
|
|
||||||
function saveStyle(style) {
|
function saveStyle(style) {
|
||||||
const id = Number(style.id) || null;
|
|
||||||
const reason = style.reason;
|
|
||||||
const notify = style.notify !== false;
|
|
||||||
delete style.method;
|
|
||||||
delete style.reason;
|
|
||||||
delete style.notify;
|
|
||||||
if (!style.name) {
|
|
||||||
delete style.name;
|
|
||||||
}
|
|
||||||
let existed;
|
|
||||||
let codeIsUpdated;
|
|
||||||
return maybeCalcDigest()
|
|
||||||
.then(maybeImportFix)
|
|
||||||
.then(decide);
|
|
||||||
|
|
||||||
function maybeCalcDigest() {
|
|
||||||
if (['install', 'update', 'update-digest'].includes(reason)) {
|
|
||||||
return calcStyleDigest(style).then(digest => {
|
|
||||||
style.originalDigest = digest;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
function maybeImportFix() {
|
|
||||||
if (reason === 'import') {
|
|
||||||
style.originalDigest = style.originalDigest || style.styleDigest; // TODO: remove in the future
|
|
||||||
delete style.styleDigest; // TODO: remove in the future
|
|
||||||
if (typeof style.originalDigest !== 'string' || style.originalDigest.length !== 40) {
|
|
||||||
delete style.originalDigest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function decide() {
|
|
||||||
if (id !== null) {
|
|
||||||
// Update or create
|
|
||||||
style.id = id;
|
|
||||||
return dbExec('get', id).then((event, store) => {
|
|
||||||
const oldStyle = event.target.result;
|
|
||||||
existed = Boolean(oldStyle);
|
|
||||||
if (reason === 'update-digest' && oldStyle.originalDigest === style.originalDigest) {
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
codeIsUpdated = !existed
|
|
||||||
|| 'sections' in style && !styleSectionsEqual(style, oldStyle)
|
|
||||||
|| reason === 'exclusionsUpdated';
|
|
||||||
style = Object.assign({installDate: Date.now()}, oldStyle, style);
|
|
||||||
return write(style, store);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Create
|
|
||||||
delete style.id;
|
|
||||||
style = Object.assign({
|
|
||||||
// Set optional things if they're undefined
|
|
||||||
enabled: true,
|
|
||||||
updateUrl: null,
|
|
||||||
md5Url: null,
|
|
||||||
url: null,
|
|
||||||
originalMd5: null,
|
|
||||||
installDate: Date.now(),
|
|
||||||
exclusions: {}
|
|
||||||
}, style);
|
|
||||||
return write(style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function write(style, store) {
|
|
||||||
style.sections = normalizeStyleSections(style);
|
|
||||||
if (store) {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
store.put(style).onsuccess = event => resolve(done(event));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return dbExec('put', style).then(done);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function done(event) {
|
|
||||||
if (reason === 'update-digest') {
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
style.id = style.id || event.target.result;
|
|
||||||
invalidateCache(existed ? {updated: style} : {added: style});
|
|
||||||
if (notify) {
|
|
||||||
const method = reason === 'exclusionsUpdated' ? reason :
|
|
||||||
existed ? 'styleUpdated' : 'styleAdded';
|
|
||||||
notifyAllTabs({method, style, codeIsUpdated, reason});
|
|
||||||
}
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
212
background/style-manager.js
Normal file
212
background/style-manager.js
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
const styleManager = (() => {
|
||||||
|
const preparing = prepare();
|
||||||
|
const styles = new Map;
|
||||||
|
const cachedStyleForUrl = createCache();
|
||||||
|
const compiledRe = createCache();
|
||||||
|
const compiledExclusion = createCache();
|
||||||
|
const BAD_MATCHER = {test: () => false};
|
||||||
|
|
||||||
|
// FIXME: do we have to prepare `styles` map for all methods?
|
||||||
|
return ensurePrepared({
|
||||||
|
getSectionsForURL,
|
||||||
|
installStyle,
|
||||||
|
deleteStyle,
|
||||||
|
setStyleExclusions,
|
||||||
|
editSave
|
||||||
|
// TODO: get all styles API?
|
||||||
|
// TODO: get style by ID?
|
||||||
|
});
|
||||||
|
|
||||||
|
function editSave() {}
|
||||||
|
|
||||||
|
function setStyleExclusions() {}
|
||||||
|
|
||||||
|
function ensurePrepared(methods) {
|
||||||
|
for (const [name, fn] in Object.entries(methods)) {
|
||||||
|
methods[name] = (...args) =>
|
||||||
|
preparing.then(() => fn(...args));
|
||||||
|
}
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteStyle(id) {
|
||||||
|
return db.exec('delete', id)
|
||||||
|
.then(() => {
|
||||||
|
// FIXME: do we really need to clear the entire cache?
|
||||||
|
cachedStyleForUrl.clear();
|
||||||
|
notifyAllTabs({method: 'styleDeleted', id});
|
||||||
|
return id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function installStyle(style) {
|
||||||
|
return calcStyleDigest(style)
|
||||||
|
.then(digest => {
|
||||||
|
style.originalDigest = digest;
|
||||||
|
return saveStyle(style);
|
||||||
|
})
|
||||||
|
.then(style => {
|
||||||
|
// FIXME: do we really need to clear the entire cache?
|
||||||
|
cachedStyleForUrl.clear();
|
||||||
|
// FIXME: invalid signature
|
||||||
|
notifyAllTabs();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function importStyle(style) {
|
||||||
|
// FIXME: move this to importer
|
||||||
|
// style.originalDigest = style.originalDigest || style.styleDigest; // TODO: remove in the future
|
||||||
|
// delete style.styleDigest; // TODO: remove in the future
|
||||||
|
// if (typeof style.originalDigest !== 'string' || style.originalDigest.length !== 40) {
|
||||||
|
// delete style.originalDigest;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveStyle(style) {
|
||||||
|
return (style.id == null ? getNewStyle() : getOldStyle())
|
||||||
|
.then(oldStyle => {
|
||||||
|
// FIXME: update installDate?
|
||||||
|
style = Object.assign(oldStyle, style);
|
||||||
|
style.sections = normalizeStyleSections(style);
|
||||||
|
return dbExec('put', style);
|
||||||
|
})
|
||||||
|
.then(event => {
|
||||||
|
if (style.id == null) {
|
||||||
|
style.id = event.target.result;
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
});
|
||||||
|
|
||||||
|
function getOldStyle() {
|
||||||
|
return db.exec('get', style.id)
|
||||||
|
.then((event, store) => {
|
||||||
|
if (!event.target.result) {
|
||||||
|
throw new Error(`Unknown style id: ${style.id}`);
|
||||||
|
}
|
||||||
|
return event.target.result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: don't overwrite style name when the name is empty
|
||||||
|
|
||||||
|
function getNewStyle() {
|
||||||
|
return Promise.resolve({
|
||||||
|
enabled: true,
|
||||||
|
updateUrl: null,
|
||||||
|
md5Url: null,
|
||||||
|
url: null,
|
||||||
|
originalMd5: null,
|
||||||
|
installDate: Date.now()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSectionsForURL(url) {
|
||||||
|
// if (!URLS.supported(url) || prefs.get('disableAll')) {
|
||||||
|
// return [];
|
||||||
|
// }
|
||||||
|
let result = cachedStyleForUrl.get(url);
|
||||||
|
if (!result) {
|
||||||
|
result = [];
|
||||||
|
for (const style of styles) {
|
||||||
|
if (!urlMatchStyle(url, style)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const item = {
|
||||||
|
id: style.id,
|
||||||
|
code: ''
|
||||||
|
};
|
||||||
|
for (const section of style.sections) {
|
||||||
|
if (urlMatchSection(url, section)) {
|
||||||
|
item.code += section.code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.code) {
|
||||||
|
result.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepare() {
|
||||||
|
return db.exec('getAll').then(event => {
|
||||||
|
const styleList = event.target.result || [];
|
||||||
|
for (const style of styleList) {
|
||||||
|
styles.set(style.id, style);
|
||||||
|
if (!style.name) {
|
||||||
|
style.name = 'ID: ' + style.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function urlMatchStyle(url, style) {
|
||||||
|
if (style.exclusions && style.exclusions.some(e => compileExclusion(e).test(url)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function urlMatchSection(url, section) {
|
||||||
|
// FIXME: match sub domains?
|
||||||
|
if (section.domains && section.domains.includes(getDomain(url))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (section.urlPrefixes && section.urlPrefixes.some(p => url.startsWith(p))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (section.urls && section.urls.includes(getUrlNoHash(url))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (section.regexps && section.regexps.some(r => compileRe(r).test(url))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileRe(text) {
|
||||||
|
let re = compiledRe.get(text);
|
||||||
|
if (!re) {
|
||||||
|
// FIXME: it should be `$({text})$` but we don't use the standard for compatibility
|
||||||
|
re = tryRegExp(`^${text}$`);
|
||||||
|
if (!re) {
|
||||||
|
re = BAD_MATCHER;
|
||||||
|
}
|
||||||
|
compiledRe.set(text, re);
|
||||||
|
}
|
||||||
|
return re;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileExclusion(text) {
|
||||||
|
let re = compiledExclusion.get(text);
|
||||||
|
if (!re) {
|
||||||
|
re = tryRegExp(buildGlob(text));
|
||||||
|
if (!re) {
|
||||||
|
re = BAD_MATCHER;
|
||||||
|
}
|
||||||
|
compiledExclusion.set(text, re);
|
||||||
|
}
|
||||||
|
return re;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildGlob(text) {
|
||||||
|
const prefix = text[0] === '^' ? '' : '\\b';
|
||||||
|
const suffix = text[text.length - 1] === '$' ? '' : '\\b';
|
||||||
|
return `${prefix}${escape(text)}${suffix}`;
|
||||||
|
|
||||||
|
function escape(text) {
|
||||||
|
// FIXME: using .* everywhere is slow
|
||||||
|
return text.replace(/[.*]/g, m => m === '.' ? '\\.' : '.*');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDomain(url) {
|
||||||
|
// FIXME: use a naive regexp
|
||||||
|
return url.match(/\w+:\/\//);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUrlNoHash(url) {
|
||||||
|
return url.split('#')[0];
|
||||||
|
}
|
||||||
|
})();
|
48
js/cache.js
Normal file
48
js/cache.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
function createCache(size = 1000) {
|
||||||
|
const map = new Map;
|
||||||
|
const buffer = Array(size);
|
||||||
|
let index = 0;
|
||||||
|
let lastIndex = 0;
|
||||||
|
return {
|
||||||
|
get,
|
||||||
|
set,
|
||||||
|
delete: delete_,
|
||||||
|
clear,
|
||||||
|
has: id => map.has(id),
|
||||||
|
get size: () => map.size
|
||||||
|
};
|
||||||
|
|
||||||
|
function get(id) {
|
||||||
|
const item = map.get(id);
|
||||||
|
return item && item.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function set(id, data) {
|
||||||
|
if (map.size === size) {
|
||||||
|
// full
|
||||||
|
map.delete(buffer[lastIndex].id);
|
||||||
|
lastIndex = (lastIndex + 1) % size;
|
||||||
|
}
|
||||||
|
const item = {id, data, index};
|
||||||
|
map.set(id, item);
|
||||||
|
buffer[index] = item;
|
||||||
|
index = (index + 1) % size;
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete_(id) {
|
||||||
|
const item = map.get(id);
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
map.delete(item.id);
|
||||||
|
const lastItem = buffer[lastIndex];
|
||||||
|
lastItem.index = item.index;
|
||||||
|
buffer[item.index] = lastItem;
|
||||||
|
lastIndex = (lastIndex + 1) % size;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
map.clear();
|
||||||
|
index = lastIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user