mirror of
https://github.com/gorhill/uBlock.git
synced 2024-11-10 09:07:54 +01:00
Work toward modernizing code base: promisification
Swathes of code have been converted to use Promises/async/await. More left to do. In the process, a regression affecting the fix to <https://github.com/uBlockOrigin/uBlock-issues/issues/682> has been fixed.
This commit is contained in:
parent
6c7d3a40d6
commit
e27328f931
7 changed files with 1032 additions and 1207 deletions
|
@ -43,6 +43,15 @@ vAPI.lastError = function() {
|
||||||
return chrome.runtime.lastError;
|
return chrome.runtime.lastError;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
vAPI.apiIsPromisified = (( ) => {
|
||||||
|
try {
|
||||||
|
return browser.storage.local.get('_') instanceof Promise;
|
||||||
|
}
|
||||||
|
catch(ex) {
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})();
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/875
|
// https://github.com/gorhill/uBlock/issues/875
|
||||||
// https://code.google.com/p/chromium/issues/detail?id=410868#c8
|
// https://code.google.com/p/chromium/issues/detail?id=410868#c8
|
||||||
// Must not leave `lastError` unchecked.
|
// Must not leave `lastError` unchecked.
|
||||||
|
@ -107,9 +116,83 @@ vAPI.app = {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// chrome.storage.local.get(null, function(bin){ console.debug('%o', bin); });
|
vAPI.storage = (( ) => {
|
||||||
|
if ( vAPI.apiIsPromisified ) {
|
||||||
vAPI.storage = browser.storage.local;
|
return browser.storage.local;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
clear: function(callback) {
|
||||||
|
if ( callback !== undefined ) {
|
||||||
|
return browser.storage.local.clear(...arguments);
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
browser.storage.local.clear(( ) => {
|
||||||
|
const lastError = browser.runtime.lastError;
|
||||||
|
if ( lastError instanceof Object ) {
|
||||||
|
return reject(lastError);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
get: function(keys, callback) {
|
||||||
|
if ( callback !== undefined ) {
|
||||||
|
return browser.storage.local.get(...arguments);
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
browser.storage.local.get(keys, result => {
|
||||||
|
const lastError = browser.runtime.lastError;
|
||||||
|
if ( lastError instanceof Object ) {
|
||||||
|
return reject(lastError);
|
||||||
|
}
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getBytesInUse: function(keys, callback) {
|
||||||
|
if ( callback !== undefined ) {
|
||||||
|
return browser.storage.local.getBytesInUse(...arguments);
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
browser.storage.local.getBytesInUse(keys, result => {
|
||||||
|
const lastError = browser.runtime.lastError;
|
||||||
|
if ( lastError instanceof Object ) {
|
||||||
|
return reject(lastError);
|
||||||
|
}
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
remove: function(keys, callback) {
|
||||||
|
if ( callback !== undefined ) {
|
||||||
|
return browser.storage.local.remove(...arguments);
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
browser.storage.local.remove(keys, ( ) => {
|
||||||
|
const lastError = browser.runtime.lastError;
|
||||||
|
if ( lastError instanceof Object ) {
|
||||||
|
return reject(lastError);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
set: function(items, callback) {
|
||||||
|
if ( callback !== undefined ) {
|
||||||
|
return browser.storage.local.set(...arguments);
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
browser.storage.local.set(items, ( ) => {
|
||||||
|
const lastError = browser.runtime.lastError;
|
||||||
|
if ( lastError instanceof Object ) {
|
||||||
|
return reject(lastError);
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -1297,26 +1380,48 @@ vAPI.commands = chrome.commands;
|
||||||
// https://github.com/gorhill/uBlock/issues/900
|
// https://github.com/gorhill/uBlock/issues/900
|
||||||
// Also, UC Browser: http://www.upsieutoc.com/image/WXuH
|
// Also, UC Browser: http://www.upsieutoc.com/image/WXuH
|
||||||
|
|
||||||
vAPI.adminStorage = chrome.storage.managed && {
|
vAPI.adminStorage = (( ) => {
|
||||||
getItem: function(key, callback) {
|
if ( browser.storage.managed instanceof Object === false ) {
|
||||||
const onRead = function(store) {
|
return {
|
||||||
|
getItem: function() {
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const managedStorage = vAPI.apiIsPromisified
|
||||||
|
? browser.storage.managed
|
||||||
|
: {
|
||||||
|
get: function(keys) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
browser.storage.managed.get(keys, result => {
|
||||||
|
const lastError = browser.runtime.lastError;
|
||||||
|
if ( lastError instanceof Object ) {
|
||||||
|
return reject(lastError);
|
||||||
|
}
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
getItem: async function(key) {
|
||||||
|
let bin;
|
||||||
|
try {
|
||||||
|
bin = await managedStorage.get(key);
|
||||||
|
} catch(ex) {
|
||||||
|
}
|
||||||
let data;
|
let data;
|
||||||
if (
|
if (
|
||||||
!chrome.runtime.lastError &&
|
chrome.runtime.lastError instanceof Object === false &&
|
||||||
typeof store === 'object' &&
|
bin instanceof Object
|
||||||
store !== null
|
|
||||||
) {
|
) {
|
||||||
data = store[key];
|
data = bin[key];
|
||||||
|
}
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
callback(data);
|
|
||||||
};
|
};
|
||||||
try {
|
})();
|
||||||
chrome.storage.managed.get(key, onRead);
|
|
||||||
} catch (ex) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
649
src/js/assets.js
649
src/js/assets.js
|
@ -27,10 +27,9 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const reIsExternalPath = /^(?:[a-z-]+):\/\//,
|
const reIsExternalPath = /^(?:[a-z-]+):\/\//;
|
||||||
reIsUserAsset = /^user-/,
|
const reIsUserAsset = /^user-/;
|
||||||
errorCantConnectTo = vAPI.i18n('errorCantConnectTo'),
|
const errorCantConnectTo = vAPI.i18n('errorCantConnectTo');
|
||||||
noopfunc = function(){};
|
|
||||||
|
|
||||||
const api = {};
|
const api = {};
|
||||||
|
|
||||||
|
@ -82,18 +81,28 @@ api.fetch = function(url, options = {}) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fail = function(details, msg) {
|
||||||
|
µBlock.logger.writeOne({
|
||||||
|
realm: 'message',
|
||||||
|
type: 'error',
|
||||||
|
text: msg,
|
||||||
|
});
|
||||||
|
details.content = '';
|
||||||
|
details.error = msg;
|
||||||
|
reject(details);
|
||||||
|
};
|
||||||
|
|
||||||
// https://github.com/gorhill/uMatrix/issues/15
|
// https://github.com/gorhill/uMatrix/issues/15
|
||||||
const onLoadEvent = function() {
|
const onLoadEvent = function() {
|
||||||
cleanup();
|
cleanup();
|
||||||
// xhr for local files gives status 0, but actually succeeds
|
// xhr for local files gives status 0, but actually succeeds
|
||||||
const details = {
|
const details = {
|
||||||
url,
|
url,
|
||||||
content: '',
|
|
||||||
statusCode: this.status || 200,
|
statusCode: this.status || 200,
|
||||||
statusText: this.statusText || ''
|
statusText: this.statusText || ''
|
||||||
};
|
};
|
||||||
if ( details.statusCode < 200 || details.statusCode >= 300 ) {
|
if ( details.statusCode < 200 || details.statusCode >= 300 ) {
|
||||||
return reject(details);
|
return fail(details, `${url}: ${details.statusCode} ${details.statusText}`);
|
||||||
}
|
}
|
||||||
details.content = this.response;
|
details.content = this.response;
|
||||||
resolve(details);
|
resolve(details);
|
||||||
|
@ -101,12 +110,7 @@ api.fetch = function(url, options = {}) {
|
||||||
|
|
||||||
const onErrorEvent = function() {
|
const onErrorEvent = function() {
|
||||||
cleanup();
|
cleanup();
|
||||||
µBlock.logger.writeOne({
|
fail({ url }, errorCantConnectTo.replace('{{msg}}', url));
|
||||||
realm: 'message',
|
|
||||||
type: 'error',
|
|
||||||
text: errorCantConnectTo.replace('{{msg}}', url)
|
|
||||||
});
|
|
||||||
reject({ url, content: '' });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTimeout = function() {
|
const onTimeout = function() {
|
||||||
|
@ -146,7 +150,7 @@ api.fetch = function(url, options = {}) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
api.fetchText = function(url, onLoad, onError) {
|
api.fetchText = async function(url) {
|
||||||
const isExternal = reIsExternalPath.test(url);
|
const isExternal = reIsExternalPath.test(url);
|
||||||
let actualUrl = isExternal ? url : vAPI.getURL(url);
|
let actualUrl = isExternal ? url : vAPI.getURL(url);
|
||||||
|
|
||||||
|
@ -171,41 +175,31 @@ api.fetchText = function(url, onLoad, onError) {
|
||||||
actualUrl += queryValue;
|
actualUrl += queryValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( typeof onError !== 'function' ) {
|
let details = { content: '' };
|
||||||
onError = onLoad;
|
try {
|
||||||
}
|
details = await api.fetch(actualUrl);
|
||||||
|
|
||||||
const onResolve = function(details) {
|
// Consider an empty result to be an error
|
||||||
if ( onLoad instanceof Function ) {
|
|
||||||
return onLoad(details);
|
|
||||||
}
|
|
||||||
return details;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onReject = function(details) {
|
|
||||||
details.content = '';
|
|
||||||
if ( onError instanceof Function ) {
|
|
||||||
return onError(details);
|
|
||||||
}
|
|
||||||
return details;
|
|
||||||
};
|
|
||||||
|
|
||||||
return api.fetch(url).then(details => {
|
|
||||||
// consider an empty result to be an error
|
|
||||||
if ( stringIsNotEmpty(details.content) === false ) {
|
if ( stringIsNotEmpty(details.content) === false ) {
|
||||||
return onReject(details);
|
details.content = '';
|
||||||
}
|
}
|
||||||
// we never download anything else than plain text: discard if response
|
|
||||||
// appears to be a HTML document: could happen when server serves
|
// We never download anything else than plain text: discard if
|
||||||
// some kind of error page I suppose
|
// response appears to be a HTML document: could happen when server
|
||||||
|
// serves some kind of error page for example.
|
||||||
const text = details.content.trim();
|
const text = details.content.trim();
|
||||||
if ( text.startsWith('<') && text.endsWith('>') ) {
|
if ( text.startsWith('<') && text.endsWith('>') ) {
|
||||||
return onReject(details);
|
details.content = '';
|
||||||
}
|
}
|
||||||
return onResolve(details);
|
} catch(ex) {
|
||||||
}).catch(details => {
|
details = ex;
|
||||||
return onReject(details);
|
}
|
||||||
});
|
|
||||||
|
// We want to return the caller's URL, not our internal one which may
|
||||||
|
// differ from the caller's one.
|
||||||
|
details.url = url;
|
||||||
|
|
||||||
|
return details;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -213,116 +207,83 @@ api.fetchText = function(url, onLoad, onError) {
|
||||||
// https://github.com/gorhill/uBlock/issues/3331
|
// https://github.com/gorhill/uBlock/issues/3331
|
||||||
// Support the seamless loading of sublists.
|
// Support the seamless loading of sublists.
|
||||||
|
|
||||||
api.fetchFilterList = function(mainlistURL, onLoad, onError) {
|
api.fetchFilterList = async function(mainlistURL) {
|
||||||
const content = [];
|
const toParsedURL = url => {
|
||||||
const pendingSublistURLs = new Set([ mainlistURL ]);
|
try {
|
||||||
const loadedSublistURLs = new Set();
|
return new URL(url);
|
||||||
const toParsedURL = api.fetchFilterList.toParsedURL;
|
} catch (ex) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// https://github.com/NanoAdblocker/NanoCore/issues/239
|
// https://github.com/NanoAdblocker/NanoCore/issues/239
|
||||||
// Anything under URL's root directory is allowed to be fetched. The
|
// Anything under URL's root directory is allowed to be fetched. The
|
||||||
// URL of a sublist will always be relative to the URL of the parent
|
// URL of a sublist will always be relative to the URL of the parent
|
||||||
// list (instead of the URL of the root list).
|
// list (instead of the URL of the root list).
|
||||||
const rootDirectoryURL = toParsedURL(mainlistURL);
|
let rootDirectoryURL = toParsedURL(mainlistURL);
|
||||||
if ( rootDirectoryURL !== undefined ) {
|
if ( rootDirectoryURL !== undefined ) {
|
||||||
const pos = rootDirectoryURL.pathname.lastIndexOf('/');
|
const pos = rootDirectoryURL.pathname.lastIndexOf('/');
|
||||||
if ( pos !== -1 ) {
|
if ( pos !== -1 ) {
|
||||||
rootDirectoryURL.pathname =
|
rootDirectoryURL.pathname =
|
||||||
rootDirectoryURL.pathname.slice(0, pos + 1);
|
rootDirectoryURL.pathname.slice(0, pos + 1);
|
||||||
|
} else {
|
||||||
|
rootDirectoryURL = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let errored = false;
|
const sublistURLs = new Set();
|
||||||
|
|
||||||
const processIncludeDirectives = function(details) {
|
const processIncludeDirectives = function(results) {
|
||||||
const reInclude = /^!#include +(\S+)/gm;
|
|
||||||
const out = [];
|
const out = [];
|
||||||
const content = details.content;
|
const reInclude = /^!#include +(\S+)/gm;
|
||||||
|
for ( const result of results ) {
|
||||||
|
if ( result instanceof Object === false ) {
|
||||||
|
out.push(result);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const content = result.content;
|
||||||
let lastIndex = 0;
|
let lastIndex = 0;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
if ( rootDirectoryURL === undefined ) { break; }
|
||||||
const match = reInclude.exec(content);
|
const match = reInclude.exec(content);
|
||||||
if ( match === null ) { break; }
|
if ( match === null ) { break; }
|
||||||
if ( toParsedURL(match[1]) !== undefined ) { continue; }
|
if ( toParsedURL(match[1]) !== undefined ) { continue; }
|
||||||
if ( match[1].indexOf('..') !== -1 ) { continue; }
|
if ( match[1].indexOf('..') !== -1 ) { continue; }
|
||||||
const subURL = toParsedURL(details.url);
|
const subURL = toParsedURL(result.url);
|
||||||
subURL.pathname = subURL.pathname.replace(/[^/]+$/, match[1]);
|
subURL.pathname = subURL.pathname.replace(/[^/]+$/, match[1]);
|
||||||
if ( subURL.href.startsWith(rootDirectoryURL.href) === false ) {
|
if ( subURL.href.startsWith(rootDirectoryURL.href) === false ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if ( pendingSublistURLs.has(subURL.href) ) { continue; }
|
if ( sublistURLs.has(subURL.href) ) { continue; }
|
||||||
if ( loadedSublistURLs.has(subURL.href) ) { continue; }
|
sublistURLs.add(subURL.href);
|
||||||
pendingSublistURLs.add(subURL.href);
|
out.push(
|
||||||
api.fetchText(subURL.href, onLocalLoadSuccess, onLocalLoadError);
|
content.slice(lastIndex, match.index).trim(),
|
||||||
out.push(content.slice(lastIndex, match.index).trim(), subURL.href);
|
`\n! >>>>>>>> ${subURL.href}\n`,
|
||||||
|
api.fetchText(subURL.href),
|
||||||
|
`! <<<<<<<< ${subURL.href}\n`
|
||||||
|
);
|
||||||
lastIndex = reInclude.lastIndex;
|
lastIndex = reInclude.lastIndex;
|
||||||
}
|
}
|
||||||
out.push(lastIndex === 0 ? content : content.slice(lastIndex).trim());
|
out.push(lastIndex === 0 ? content : content.slice(lastIndex).trim());
|
||||||
|
}
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLocalLoadSuccess = function(details) {
|
|
||||||
if ( errored ) { return; }
|
|
||||||
|
|
||||||
const isSublist = details.url !== mainlistURL;
|
|
||||||
|
|
||||||
pendingSublistURLs.delete(details.url);
|
|
||||||
loadedSublistURLs.add(details.url);
|
|
||||||
|
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/329
|
|
||||||
// Insert fetched content at position of related #!include directive
|
|
||||||
let slot = isSublist ? content.indexOf(details.url) : 0;
|
|
||||||
if ( isSublist ) {
|
|
||||||
content.splice(
|
|
||||||
slot,
|
|
||||||
1,
|
|
||||||
'! >>>>>>>> ' + details.url,
|
|
||||||
details.content.trim(),
|
|
||||||
'! <<<<<<<< ' + details.url
|
|
||||||
);
|
|
||||||
slot += 1;
|
|
||||||
} else {
|
|
||||||
content[0] = details.content.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find and process #!include directives
|
|
||||||
if (
|
|
||||||
rootDirectoryURL !== undefined &&
|
|
||||||
rootDirectoryURL.pathname.length > 0
|
|
||||||
) {
|
|
||||||
const processed = processIncludeDirectives(details);
|
|
||||||
if ( processed.length > 1 ) {
|
|
||||||
content.splice(slot, 1, ...processed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( pendingSublistURLs.size !== 0 ) { return; }
|
|
||||||
|
|
||||||
details.url = mainlistURL;
|
|
||||||
details.content = content.join('\n').trim();
|
|
||||||
onLoad(details);
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://github.com/AdguardTeam/FiltersRegistry/issues/82
|
// https://github.com/AdguardTeam/FiltersRegistry/issues/82
|
||||||
// Not checking for `errored` status was causing repeated notifications
|
// Not checking for `errored` status was causing repeated notifications
|
||||||
// to the caller. This can happen when more than one out of multiple
|
// to the caller. This can happen when more than one out of multiple
|
||||||
// sublists can't be fetched.
|
// sublists can't be fetched.
|
||||||
const onLocalLoadError = function(details) {
|
|
||||||
if ( errored ) { return; }
|
|
||||||
|
|
||||||
errored = true;
|
let allParts = [
|
||||||
details.url = mainlistURL;
|
this.fetchText(mainlistURL)
|
||||||
details.content = '';
|
];
|
||||||
onError(details);
|
for (;;) {
|
||||||
};
|
allParts = processIncludeDirectives(await Promise.all(allParts));
|
||||||
|
if ( allParts.every(v => typeof v === 'string') ) { break; }
|
||||||
this.fetchText(mainlistURL, onLocalLoadSuccess, onLocalLoadError);
|
|
||||||
};
|
|
||||||
|
|
||||||
api.fetchFilterList.toParsedURL = function(url) {
|
|
||||||
try {
|
|
||||||
return new URL(url);
|
|
||||||
} catch (ex) {
|
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
url: mainlistURL,
|
||||||
|
content: allParts.join('')
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
@ -341,6 +302,34 @@ api.fetchFilterList.toParsedURL = function(url) {
|
||||||
let assetSourceRegistryPromise,
|
let assetSourceRegistryPromise,
|
||||||
assetSourceRegistry = Object.create(null);
|
assetSourceRegistry = Object.create(null);
|
||||||
|
|
||||||
|
const getAssetSourceRegistry = function() {
|
||||||
|
if ( assetSourceRegistryPromise === undefined ) {
|
||||||
|
assetSourceRegistryPromise = µBlock.cacheStorage.get(
|
||||||
|
'assetSourceRegistry'
|
||||||
|
).then(bin => {
|
||||||
|
if (
|
||||||
|
bin instanceof Object &&
|
||||||
|
bin.assetSourceRegistry instanceof Object
|
||||||
|
) {
|
||||||
|
assetSourceRegistry = bin.assetSourceRegistry;
|
||||||
|
return assetSourceRegistry;
|
||||||
|
}
|
||||||
|
return api.fetchText(
|
||||||
|
µBlock.assetsBootstrapLocation || 'assets/assets.json'
|
||||||
|
).then(details => {
|
||||||
|
return details.content !== ''
|
||||||
|
? details
|
||||||
|
: api.fetchText('assets/assets.json');
|
||||||
|
}).then(details => {
|
||||||
|
updateAssetSourceRegistry(details.content, true);
|
||||||
|
return assetSourceRegistry;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return assetSourceRegistryPromise;
|
||||||
|
};
|
||||||
|
|
||||||
const registerAssetSource = function(assetKey, dict) {
|
const registerAssetSource = function(assetKey, dict) {
|
||||||
const entry = assetSourceRegistry[assetKey] || {};
|
const entry = assetSourceRegistry[assetKey] || {};
|
||||||
for ( const prop in dict ) {
|
for ( const prop in dict ) {
|
||||||
|
@ -383,7 +372,7 @@ const unregisterAssetSource = function(assetKey) {
|
||||||
delete assetSourceRegistry[assetKey];
|
delete assetSourceRegistry[assetKey];
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveAssetSourceRegistry = (function() {
|
const saveAssetSourceRegistry = (( ) => {
|
||||||
let timer;
|
let timer;
|
||||||
const save = function() {
|
const save = function() {
|
||||||
timer = undefined;
|
timer = undefined;
|
||||||
|
@ -433,46 +422,16 @@ const updateAssetSourceRegistry = function(json, silent) {
|
||||||
saveAssetSourceRegistry();
|
saveAssetSourceRegistry();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAssetSourceRegistry = function() {
|
api.registerAssetSource = async function(assetKey, details) {
|
||||||
if ( assetSourceRegistryPromise === undefined ) {
|
await getAssetSourceRegistry();
|
||||||
assetSourceRegistryPromise = µBlock.cacheStorage.get(
|
|
||||||
'assetSourceRegistry'
|
|
||||||
).then(bin => {
|
|
||||||
if (
|
|
||||||
bin instanceof Object &&
|
|
||||||
bin.assetSourceRegistry instanceof Object
|
|
||||||
) {
|
|
||||||
assetSourceRegistry = bin.assetSourceRegistry;
|
|
||||||
return assetSourceRegistry;
|
|
||||||
}
|
|
||||||
return api.fetchText(
|
|
||||||
µBlock.assetsBootstrapLocation || 'assets/assets.json'
|
|
||||||
).then(details => {
|
|
||||||
return details.content !== ''
|
|
||||||
? details
|
|
||||||
: api.fetchText('assets/assets.json');
|
|
||||||
}).then(details => {
|
|
||||||
updateAssetSourceRegistry(details.content, true);
|
|
||||||
return assetSourceRegistry;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return assetSourceRegistryPromise;
|
|
||||||
};
|
|
||||||
|
|
||||||
api.registerAssetSource = function(assetKey, details) {
|
|
||||||
getAssetSourceRegistry().then(( ) => {
|
|
||||||
registerAssetSource(assetKey, details);
|
registerAssetSource(assetKey, details);
|
||||||
saveAssetSourceRegistry(true);
|
saveAssetSourceRegistry(true);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
api.unregisterAssetSource = function(assetKey) {
|
api.unregisterAssetSource = async function(assetKey) {
|
||||||
getAssetSourceRegistry().then(( ) => {
|
await getAssetSourceRegistry();
|
||||||
unregisterAssetSource(assetKey);
|
unregisterAssetSource(assetKey);
|
||||||
saveAssetSourceRegistry(true);
|
saveAssetSourceRegistry(true);
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
@ -497,13 +456,14 @@ const getAssetCacheRegistry = function() {
|
||||||
) {
|
) {
|
||||||
assetCacheRegistry = bin.assetCacheRegistry;
|
assetCacheRegistry = bin.assetCacheRegistry;
|
||||||
}
|
}
|
||||||
|
return assetCacheRegistry;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return assetCacheRegistryPromise.then(( ) => assetCacheRegistry);
|
return assetCacheRegistryPromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveAssetCacheRegistry = (function() {
|
const saveAssetCacheRegistry = (( ) => {
|
||||||
let timer;
|
let timer;
|
||||||
const save = function() {
|
const save = function() {
|
||||||
timer = undefined;
|
timer = undefined;
|
||||||
|
@ -519,42 +479,38 @@ const saveAssetCacheRegistry = (function() {
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const assetCacheRead = function(assetKey, callback) {
|
const assetCacheRead = async function(assetKey) {
|
||||||
const internalKey = 'cache/' + assetKey;
|
const internalKey = `cache/${assetKey}`;
|
||||||
|
|
||||||
const reportBack = function(content) {
|
const reportBack = function(content) {
|
||||||
if ( content instanceof Blob ) { content = ''; }
|
if ( content instanceof Blob ) { content = ''; }
|
||||||
let details = { assetKey: assetKey, content: content };
|
let details = { assetKey: assetKey, content: content };
|
||||||
if ( content === '' ) { details.error = 'E_NOTFOUND'; }
|
if ( content === '' ) { details.error = 'ENOTFOUND'; }
|
||||||
callback(details);
|
return details;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAssetRead = function(bin) {
|
const [ , bin ] = await Promise.all([
|
||||||
|
getAssetCacheRegistry(),
|
||||||
|
µBlock.cacheStorage.get(internalKey),
|
||||||
|
]);
|
||||||
if (
|
if (
|
||||||
bin instanceof Object === false ||
|
bin instanceof Object === false ||
|
||||||
bin.hasOwnProperty(internalKey) === false
|
bin.hasOwnProperty(internalKey) === false
|
||||||
) {
|
) {
|
||||||
return reportBack('');
|
return reportBack('');
|
||||||
}
|
}
|
||||||
let entry = assetCacheRegistry[assetKey];
|
|
||||||
|
const entry = assetCacheRegistry[assetKey];
|
||||||
if ( entry === undefined ) {
|
if ( entry === undefined ) {
|
||||||
return reportBack('');
|
return reportBack('');
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.readTime = Date.now();
|
entry.readTime = Date.now();
|
||||||
saveAssetCacheRegistry(true);
|
saveAssetCacheRegistry(true);
|
||||||
reportBack(bin[internalKey]);
|
return reportBack(bin[internalKey]);
|
||||||
};
|
|
||||||
|
|
||||||
Promise.all([
|
|
||||||
getAssetCacheRegistry(),
|
|
||||||
µBlock.cacheStorage.get(internalKey),
|
|
||||||
]).then(results => {
|
|
||||||
onAssetRead(results[1]);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const assetCacheWrite = function(assetKey, details, callback) {
|
const assetCacheWrite = async function(assetKey, details) {
|
||||||
let internalKey = 'cache/' + assetKey;
|
|
||||||
let content = '';
|
let content = '';
|
||||||
if ( typeof details === 'string' ) {
|
if ( typeof details === 'string' ) {
|
||||||
content = details;
|
content = details;
|
||||||
|
@ -563,32 +519,32 @@ const assetCacheWrite = function(assetKey, details, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( content === '' ) {
|
if ( content === '' ) {
|
||||||
return assetCacheRemove(assetKey, callback);
|
return assetCacheRemove(assetKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
const onReady = function() {
|
const cacheDict = await getAssetCacheRegistry();
|
||||||
let entry = assetCacheRegistry[assetKey];
|
|
||||||
|
let entry = cacheDict[assetKey];
|
||||||
if ( entry === undefined ) {
|
if ( entry === undefined ) {
|
||||||
entry = assetCacheRegistry[assetKey] = {};
|
entry = cacheDict[assetKey] = {};
|
||||||
}
|
}
|
||||||
entry.writeTime = entry.readTime = Date.now();
|
entry.writeTime = entry.readTime = Date.now();
|
||||||
if ( details instanceof Object && typeof details.url === 'string' ) {
|
if ( details instanceof Object && typeof details.url === 'string' ) {
|
||||||
entry.remoteURL = details.url;
|
entry.remoteURL = details.url;
|
||||||
}
|
}
|
||||||
µBlock.cacheStorage.set({ assetCacheRegistry, [internalKey]: content });
|
µBlock.cacheStorage.set({
|
||||||
|
cacheDict,
|
||||||
|
[`cache/${assetKey}`]: content
|
||||||
|
});
|
||||||
|
|
||||||
const result = { assetKey, content };
|
const result = { assetKey, content };
|
||||||
if ( typeof callback === 'function' ) {
|
|
||||||
callback(result);
|
|
||||||
}
|
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/248
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/248
|
||||||
fireNotification('after-asset-updated', result);
|
fireNotification('after-asset-updated', result);
|
||||||
};
|
return result;
|
||||||
|
|
||||||
getAssetCacheRegistry().then(( ) => onReady());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const assetCacheRemove = function(pattern, callback) {
|
const assetCacheRemove = async function(pattern) {
|
||||||
getAssetCacheRegistry().then(cacheDict => {
|
const cacheDict = await getAssetCacheRegistry();
|
||||||
const removedEntries = [];
|
const removedEntries = [];
|
||||||
const removedContent = [];
|
const removedContent = [];
|
||||||
for ( const assetKey in cacheDict ) {
|
for ( const assetKey in cacheDict ) {
|
||||||
|
@ -606,24 +562,16 @@ const assetCacheRemove = function(pattern, callback) {
|
||||||
µBlock.cacheStorage.remove(removedContent);
|
µBlock.cacheStorage.remove(removedContent);
|
||||||
µBlock.cacheStorage.set({ assetCacheRegistry });
|
µBlock.cacheStorage.set({ assetCacheRegistry });
|
||||||
}
|
}
|
||||||
if ( typeof callback === 'function' ) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
for ( let i = 0; i < removedEntries.length; i++ ) {
|
for ( let i = 0; i < removedEntries.length; i++ ) {
|
||||||
fireNotification(
|
fireNotification(
|
||||||
'after-asset-updated',
|
'after-asset-updated',
|
||||||
{ assetKey: removedEntries[i] }
|
{ assetKey: removedEntries[i] }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const assetCacheMarkAsDirty = function(pattern, exclude, callback) {
|
const assetCacheMarkAsDirty = async function(pattern, exclude) {
|
||||||
if ( typeof exclude === 'function' ) {
|
const cacheDict = await getAssetCacheRegistry();
|
||||||
callback = exclude;
|
|
||||||
exclude = undefined;
|
|
||||||
}
|
|
||||||
getAssetCacheRegistry().then(cacheDict => {
|
|
||||||
let mustSave = false;
|
let mustSave = false;
|
||||||
for ( const assetKey in cacheDict ) {
|
for ( const assetKey in cacheDict ) {
|
||||||
if ( pattern instanceof RegExp ) {
|
if ( pattern instanceof RegExp ) {
|
||||||
|
@ -648,10 +596,6 @@ const assetCacheMarkAsDirty = function(pattern, exclude, callback) {
|
||||||
if ( mustSave ) {
|
if ( mustSave ) {
|
||||||
µBlock.cacheStorage.set({ assetCacheRegistry });
|
µBlock.cacheStorage.set({ assetCacheRegistry });
|
||||||
}
|
}
|
||||||
if ( typeof callback === 'function' ) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -678,141 +622,89 @@ const stringIsNotEmpty = function(s) {
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
const readUserAsset = function(assetKey, callback) {
|
const readUserAsset = async function(assetKey) {
|
||||||
const reportBack = function(content) {
|
const bin = await vAPI.storage.get(assetKey);
|
||||||
callback({ assetKey, content });
|
|
||||||
};
|
|
||||||
vAPI.storage.get(assetKey, bin => {
|
|
||||||
const content =
|
const content =
|
||||||
bin instanceof Object && typeof bin[assetKey] === 'string'
|
bin instanceof Object && typeof bin[assetKey] === 'string'
|
||||||
? bin[assetKey]
|
? bin[assetKey]
|
||||||
: '';
|
: '';
|
||||||
return reportBack(content);
|
|
||||||
});
|
|
||||||
// Remove obsolete entry
|
// Remove obsolete entry
|
||||||
// TODO: remove once everybody is well beyond 1.18.6
|
// TODO: remove once everybody is well beyond 1.18.6
|
||||||
vAPI.storage.remove('assets/user/filters.txt');
|
vAPI.storage.remove('assets/user/filters.txt');
|
||||||
|
|
||||||
|
return { assetKey, content };
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveUserAsset = function(assetKey, content, callback) {
|
const saveUserAsset = function(assetKey, content) {
|
||||||
vAPI.storage.set({ [assetKey]: content }, ( ) => {
|
return vAPI.storage.set({ [assetKey]: content }).then(( ) => {
|
||||||
if ( callback instanceof Function ) {
|
return { assetKey, content };
|
||||||
callback({ assetKey, content });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
api.get = function(assetKey, options, callback) {
|
api.get = async function(assetKey, options = {}) {
|
||||||
if ( typeof options === 'function' ) {
|
|
||||||
callback = options;
|
|
||||||
options = {};
|
|
||||||
} else if ( typeof callback !== 'function' ) {
|
|
||||||
callback = noopfunc;
|
|
||||||
}
|
|
||||||
// This can happen if the method was called as a thenable.
|
|
||||||
if ( options instanceof Object === false ) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
|
||||||
// start of executor
|
|
||||||
if ( assetKey === µBlock.userFiltersPath ) {
|
if ( assetKey === µBlock.userFiltersPath ) {
|
||||||
readUserAsset(assetKey, details => {
|
return readUserAsset(assetKey);
|
||||||
callback(details);
|
|
||||||
resolve(details);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let assetDetails = {},
|
let assetDetails = {};
|
||||||
contentURLs,
|
|
||||||
contentURL;
|
|
||||||
|
|
||||||
const reportBack = (content, err) => {
|
const reportBack = (content, url = '', err = undefined) => {
|
||||||
const details = { assetKey, content };
|
const details = { assetKey, content };
|
||||||
if ( err ) {
|
if ( err !== undefined ) {
|
||||||
details.error = assetDetails.lastError = err;
|
details.error = assetDetails.lastError = err;
|
||||||
} else {
|
} else {
|
||||||
assetDetails.lastError = undefined;
|
assetDetails.lastError = undefined;
|
||||||
}
|
}
|
||||||
if ( options.needSourceURL ) {
|
if ( options.needSourceURL ) {
|
||||||
if (
|
if (
|
||||||
contentURL === undefined &&
|
url === '' &&
|
||||||
assetCacheRegistry instanceof Object &&
|
assetCacheRegistry instanceof Object &&
|
||||||
assetCacheRegistry[assetKey] instanceof Object
|
assetCacheRegistry[assetKey] instanceof Object
|
||||||
) {
|
) {
|
||||||
details.sourceURL = assetCacheRegistry[assetKey].remoteURL;
|
details.sourceURL = assetCacheRegistry[assetKey].remoteURL;
|
||||||
}
|
}
|
||||||
if ( reIsExternalPath.test(contentURL) ) {
|
if ( reIsExternalPath.test(url) ) {
|
||||||
details.sourceURL = contentURL;
|
details.sourceURL = url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callback(details);
|
return details;
|
||||||
resolve(details);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onContentNotLoaded = ( ) => {
|
const details = await assetCacheRead(assetKey);
|
||||||
let isExternal;
|
|
||||||
while ( (contentURL = contentURLs.shift()) ) {
|
|
||||||
isExternal = reIsExternalPath.test(contentURL);
|
|
||||||
if ( isExternal === false || assetDetails.hasLocalURL !== true ) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( !contentURL ) {
|
|
||||||
return reportBack('', 'E_NOTFOUND');
|
|
||||||
}
|
|
||||||
if ( assetDetails.content === 'filters' ) {
|
|
||||||
api.fetchFilterList(contentURL, onContentLoaded, onContentNotLoaded);
|
|
||||||
} else {
|
|
||||||
api.fetchText(contentURL, onContentLoaded, onContentNotLoaded);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onContentLoaded = details => {
|
|
||||||
if ( stringIsNotEmpty(details.content) === false ) {
|
|
||||||
onContentNotLoaded();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ( reIsExternalPath.test(contentURL) && options.dontCache !== true ) {
|
|
||||||
assetCacheWrite(assetKey, {
|
|
||||||
content: details.content,
|
|
||||||
url: contentURL
|
|
||||||
});
|
|
||||||
}
|
|
||||||
reportBack(details.content);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onCachedContentLoaded = details => {
|
|
||||||
if ( details.content !== '' ) {
|
if ( details.content !== '' ) {
|
||||||
return reportBack(details.content);
|
return reportBack(details.content);
|
||||||
}
|
}
|
||||||
getAssetSourceRegistry().then(registry => {
|
|
||||||
assetDetails = registry[assetKey] || {};
|
const assetRegistry = await getAssetSourceRegistry();
|
||||||
|
assetDetails = assetRegistry[assetKey] || {};
|
||||||
|
let contentURLs = [];
|
||||||
if ( typeof assetDetails.contentURL === 'string' ) {
|
if ( typeof assetDetails.contentURL === 'string' ) {
|
||||||
contentURLs = [ assetDetails.contentURL ];
|
contentURLs = [ assetDetails.contentURL ];
|
||||||
} else if ( Array.isArray(assetDetails.contentURL) ) {
|
} else if ( Array.isArray(assetDetails.contentURL) ) {
|
||||||
contentURLs = assetDetails.contentURL.slice(0);
|
contentURLs = assetDetails.contentURL.slice(0);
|
||||||
} else {
|
|
||||||
contentURLs = [];
|
|
||||||
}
|
}
|
||||||
onContentNotLoaded();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
assetCacheRead(assetKey, onCachedContentLoaded);
|
for ( const contentURL of contentURLs ) {
|
||||||
// end of executor
|
if ( reIsExternalPath.test(contentURL) && assetDetails.hasLocalURL ) {
|
||||||
});
|
continue;
|
||||||
|
}
|
||||||
|
const details = assetDetails.content === 'filters'
|
||||||
|
? await api.fetchFilterList(contentURL)
|
||||||
|
: await api.fetchText(contentURL);
|
||||||
|
if ( details.content === '' ) { continue; }
|
||||||
|
return reportBack(details.content, contentURL);
|
||||||
|
}
|
||||||
|
return reportBack('', '', 'ENOTFOUND');
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const getRemote = function(assetKey, callback) {
|
const getRemote = async function(assetKey) {
|
||||||
let assetDetails = {};
|
const assetRegistry = await getAssetSourceRegistry();
|
||||||
let contentURLs;
|
const assetDetails = assetRegistry[assetKey] || {};
|
||||||
let contentURL;
|
|
||||||
|
|
||||||
const reportBack = function(content, err) {
|
const reportBack = function(content, err) {
|
||||||
const details = { assetKey: assetKey, content: content };
|
const details = { assetKey: assetKey, content: content };
|
||||||
|
@ -821,81 +713,64 @@ const getRemote = function(assetKey, callback) {
|
||||||
} else {
|
} else {
|
||||||
assetDetails.lastError = undefined;
|
assetDetails.lastError = undefined;
|
||||||
}
|
}
|
||||||
callback(details);
|
return details;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRemoteContentLoaded = function(details) {
|
let contentURLs = [];
|
||||||
if ( stringIsNotEmpty(details.content) === false ) {
|
|
||||||
registerAssetSource(assetKey, { error: { time: Date.now(), error: 'No content' } });
|
|
||||||
tryLoading();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
assetCacheWrite(assetKey, {
|
|
||||||
content: details.content,
|
|
||||||
url: contentURL
|
|
||||||
});
|
|
||||||
registerAssetSource(assetKey, { error: undefined });
|
|
||||||
reportBack(details.content);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRemoteContentError = function(details) {
|
|
||||||
let text = details.statusText;
|
|
||||||
if ( details.statusCode === 0 ) {
|
|
||||||
text = 'network error';
|
|
||||||
}
|
|
||||||
registerAssetSource(assetKey, { error: { time: Date.now(), error: text } });
|
|
||||||
tryLoading();
|
|
||||||
};
|
|
||||||
|
|
||||||
const tryLoading = function() {
|
|
||||||
while ( (contentURL = contentURLs.shift()) ) {
|
|
||||||
if ( reIsExternalPath.test(contentURL) ) { break; }
|
|
||||||
}
|
|
||||||
if ( !contentURL ) {
|
|
||||||
return reportBack('', 'E_NOTFOUND');
|
|
||||||
}
|
|
||||||
if ( assetDetails.content === 'filters' ) {
|
|
||||||
api.fetchFilterList(contentURL, onRemoteContentLoaded, onRemoteContentError);
|
|
||||||
} else {
|
|
||||||
api.fetchText(contentURL, onRemoteContentLoaded, onRemoteContentError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getAssetSourceRegistry().then(registry => {
|
|
||||||
assetDetails = registry[assetKey] || {};
|
|
||||||
if ( typeof assetDetails.contentURL === 'string' ) {
|
if ( typeof assetDetails.contentURL === 'string' ) {
|
||||||
contentURLs = [ assetDetails.contentURL ];
|
contentURLs = [ assetDetails.contentURL ];
|
||||||
} else if ( Array.isArray(assetDetails.contentURL) ) {
|
} else if ( Array.isArray(assetDetails.contentURL) ) {
|
||||||
contentURLs = assetDetails.contentURL.slice(0);
|
contentURLs = assetDetails.contentURL.slice(0);
|
||||||
} else {
|
|
||||||
contentURLs = [];
|
|
||||||
}
|
}
|
||||||
tryLoading();
|
|
||||||
});
|
for ( const contentURL of contentURLs ) {
|
||||||
|
if ( reIsExternalPath.test(contentURL) === false ) { continue; }
|
||||||
|
|
||||||
|
const result = assetDetails.content === 'filters'
|
||||||
|
? await api.fetchFilterList(contentURL)
|
||||||
|
: await api.fetchText(contentURL);
|
||||||
|
|
||||||
|
// Failure
|
||||||
|
if ( stringIsNotEmpty(result.content) === false ) {
|
||||||
|
let error = result.statusText;
|
||||||
|
if ( result.statusCode === 0 ) {
|
||||||
|
error = 'network error';
|
||||||
|
}
|
||||||
|
registerAssetSource(
|
||||||
|
assetKey,
|
||||||
|
{ error: { time: Date.now(), error } }
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
assetCacheWrite(
|
||||||
|
assetKey,
|
||||||
|
{ content: result.content, url: contentURL }
|
||||||
|
);
|
||||||
|
registerAssetSource(assetKey, { error: undefined });
|
||||||
|
return reportBack(result.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reportBack('', 'ENOTFOUND');
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
api.put = function(assetKey, content, callback) {
|
api.put = async function(assetKey, content) {
|
||||||
return new Promise(resolve => {
|
return reIsUserAsset.test(assetKey)
|
||||||
const onDone = function(details) {
|
? await saveUserAsset(assetKey, content)
|
||||||
if ( typeof callback === 'function' ) {
|
: await assetCacheWrite(assetKey, content);
|
||||||
callback(details);
|
|
||||||
}
|
|
||||||
resolve(details);
|
|
||||||
};
|
|
||||||
if ( reIsUserAsset.test(assetKey) ) {
|
|
||||||
saveUserAsset(assetKey, content, onDone);
|
|
||||||
} else {
|
|
||||||
assetCacheWrite(assetKey, content, onDone);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
api.metadata = function(callback) {
|
api.metadata = async function() {
|
||||||
const onReady = function() {
|
await Promise.all([
|
||||||
|
getAssetSourceRegistry(),
|
||||||
|
getAssetCacheRegistry(),
|
||||||
|
]);
|
||||||
|
|
||||||
const assetDict = JSON.parse(JSON.stringify(assetSourceRegistry));
|
const assetDict = JSON.parse(JSON.stringify(assetSourceRegistry));
|
||||||
const cacheDict = assetCacheRegistry;
|
const cacheDict = assetCacheRegistry;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
@ -917,27 +792,20 @@ api.metadata = function(callback) {
|
||||||
assetEntry.obsolete = true;
|
assetEntry.obsolete = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callback(assetDict);
|
|
||||||
};
|
|
||||||
|
|
||||||
Promise.all([
|
return assetDict;
|
||||||
getAssetSourceRegistry(),
|
|
||||||
getAssetCacheRegistry(),
|
|
||||||
]).then(
|
|
||||||
( ) => onReady()
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
api.purge = assetCacheMarkAsDirty;
|
api.purge = assetCacheMarkAsDirty;
|
||||||
|
|
||||||
api.remove = function(pattern, callback) {
|
api.remove = function(pattern) {
|
||||||
assetCacheRemove(pattern, callback);
|
return assetCacheRemove(pattern);
|
||||||
};
|
};
|
||||||
|
|
||||||
api.rmrf = function() {
|
api.rmrf = function() {
|
||||||
assetCacheRemove(/./);
|
return assetCacheRemove(/./);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -959,19 +827,14 @@ const updateFirst = function() {
|
||||||
updateNext();
|
updateNext();
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateNext = function() {
|
const updateNext = async function() {
|
||||||
let assetDict, cacheDict;
|
const [ assetDict, cacheDict ] = await Promise.all([
|
||||||
|
getAssetSourceRegistry(),
|
||||||
|
getAssetCacheRegistry(),
|
||||||
|
]);
|
||||||
|
|
||||||
// This will remove a cached asset when it's no longer in use.
|
|
||||||
const garbageCollectOne = function(assetKey) {
|
|
||||||
const cacheEntry = cacheDict[assetKey];
|
|
||||||
if ( cacheEntry && cacheEntry.readTime < assetCacheRegistryStartTime ) {
|
|
||||||
assetCacheRemove(assetKey);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const findOne = function() {
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
let assetKeyToUpdate;
|
||||||
for ( const assetKey in assetDict ) {
|
for ( const assetKey in assetDict ) {
|
||||||
const assetEntry = assetDict[assetKey];
|
const assetEntry = assetDict[assetKey];
|
||||||
if ( assetEntry.hasRemoteURL !== true ) { continue; }
|
if ( assetEntry.hasRemoteURL !== true ) { continue; }
|
||||||
|
@ -989,45 +852,33 @@ const updateNext = function() {
|
||||||
{ assetKey: assetKey, type: assetEntry.content }
|
{ assetKey: assetKey, type: assetEntry.content }
|
||||||
) === true
|
) === true
|
||||||
) {
|
) {
|
||||||
return assetKey;
|
assetKeyToUpdate = assetKey;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
garbageCollectOne(assetKey);
|
// This will remove a cached asset when it's no longer in use.
|
||||||
|
if (
|
||||||
|
cacheEntry &&
|
||||||
|
cacheEntry.readTime < assetCacheRegistryStartTime
|
||||||
|
) {
|
||||||
|
assetCacheRemove(assetKey);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const updatedOne = function(details) {
|
|
||||||
if ( details.content !== '' ) {
|
|
||||||
updaterUpdated.push(details.assetKey);
|
|
||||||
if ( details.assetKey === 'assets.json' ) {
|
|
||||||
updateAssetSourceRegistry(details.content);
|
|
||||||
}
|
}
|
||||||
} else {
|
if ( assetKeyToUpdate === undefined ) {
|
||||||
fireNotification('asset-update-failed', { assetKey: details.assetKey });
|
|
||||||
}
|
|
||||||
if ( findOne() !== undefined ) {
|
|
||||||
vAPI.setTimeout(updateNext, updaterAssetDelay);
|
|
||||||
} else {
|
|
||||||
updateDone();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateOne = function() {
|
|
||||||
const assetKey = findOne();
|
|
||||||
if ( assetKey === undefined ) {
|
|
||||||
return updateDone();
|
return updateDone();
|
||||||
}
|
}
|
||||||
updaterFetched.add(assetKey);
|
updaterFetched.add(assetKeyToUpdate);
|
||||||
getRemote(assetKey, updatedOne);
|
|
||||||
};
|
|
||||||
|
|
||||||
Promise.all([
|
const result = await getRemote(assetKeyToUpdate);
|
||||||
getAssetSourceRegistry(),
|
if ( result.content !== '' ) {
|
||||||
getAssetCacheRegistry(),
|
updaterUpdated.push(result.assetKey);
|
||||||
]).then(results => {
|
if ( result.assetKey === 'assets.json' ) {
|
||||||
assetDict = results[0];
|
updateAssetSourceRegistry(result.content);
|
||||||
cacheDict = results[1];
|
}
|
||||||
updateOne();
|
} else {
|
||||||
});
|
fireNotification('asset-update-failed', { assetKey: result.assetKey });
|
||||||
|
}
|
||||||
|
|
||||||
|
vAPI.setTimeout(updateNext, updaterAssetDelay);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateDone = function() {
|
const updateDone = function() {
|
||||||
|
|
|
@ -58,9 +58,10 @@ const onMessage = function(request, sender, callback) {
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/417
|
// https://github.com/chrisaljoudi/uBlock/issues/417
|
||||||
µb.assets.get(
|
µb.assets.get(
|
||||||
request.url,
|
request.url,
|
||||||
{ dontCache: true, needSourceURL: true },
|
{ dontCache: true, needSourceURL: true }
|
||||||
callback
|
).then(result => {
|
||||||
);
|
callback(result);
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'listsFromNetFilter':
|
case 'listsFromNetFilter':
|
||||||
|
@ -791,24 +792,18 @@ vAPI.messaging.listen({
|
||||||
const µb = µBlock;
|
const µb = µBlock;
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
const getLocalData = function(callback) {
|
const getLocalData = async function() {
|
||||||
const onStorageInfoReady = function(bytesInUse) {
|
const data = Object.assign({}, µb.restoreBackupSettings);
|
||||||
const o = µb.restoreBackupSettings;
|
data.storageUsed = await µb.getBytesInUse();
|
||||||
callback({
|
return data;
|
||||||
storageUsed: bytesInUse,
|
|
||||||
lastRestoreFile: o.lastRestoreFile,
|
|
||||||
lastRestoreTime: o.lastRestoreTime,
|
|
||||||
lastBackupFile: o.lastBackupFile,
|
|
||||||
lastBackupTime: o.lastBackupTime,
|
|
||||||
cloudStorageSupported: µb.cloudStorageSupported,
|
|
||||||
privacySettingsSupported: µb.privacySettingsSupported
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
µb.getBytesInUse(onStorageInfoReady);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const backupUserData = function(callback) {
|
const backupUserData = async function() {
|
||||||
|
const [ userFilters, localData ] = await Promise.all([
|
||||||
|
µb.loadUserFilters(),
|
||||||
|
getLocalData(),
|
||||||
|
]);
|
||||||
|
|
||||||
const userData = {
|
const userData = {
|
||||||
timeStamp: Date.now(),
|
timeStamp: Date.now(),
|
||||||
version: vAPI.app.version,
|
version: vAPI.app.version,
|
||||||
|
@ -821,23 +816,17 @@ const backupUserData = function(callback) {
|
||||||
dynamicFilteringString: µb.permanentFirewall.toString(),
|
dynamicFilteringString: µb.permanentFirewall.toString(),
|
||||||
urlFilteringString: µb.permanentURLFiltering.toString(),
|
urlFilteringString: µb.permanentURLFiltering.toString(),
|
||||||
hostnameSwitchesString: µb.permanentSwitches.toString(),
|
hostnameSwitchesString: µb.permanentSwitches.toString(),
|
||||||
userFilters: ''
|
userFilters: userFilters.content,
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUserFiltersReady = function(details) {
|
|
||||||
userData.userFilters = details.content;
|
|
||||||
const filename = vAPI.i18n('aboutBackupFilename')
|
const filename = vAPI.i18n('aboutBackupFilename')
|
||||||
.replace('{{datetime}}', µb.dateNowToSensibleString())
|
.replace('{{datetime}}', µb.dateNowToSensibleString())
|
||||||
.replace(/ +/g, '_');
|
.replace(/ +/g, '_');
|
||||||
µb.restoreBackupSettings.lastBackupFile = filename;
|
µb.restoreBackupSettings.lastBackupFile = filename;
|
||||||
µb.restoreBackupSettings.lastBackupTime = Date.now();
|
µb.restoreBackupSettings.lastBackupTime = Date.now();
|
||||||
vAPI.storage.set(µb.restoreBackupSettings);
|
vAPI.storage.set(µb.restoreBackupSettings);
|
||||||
getLocalData(function(localData) {
|
|
||||||
callback({ localData: localData, userData: userData });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
µb.assets.get(µb.userFiltersPath, onUserFiltersReady);
|
return { localData, userData };
|
||||||
};
|
};
|
||||||
|
|
||||||
const restoreUserData = function(request) {
|
const restoreUserData = function(request) {
|
||||||
|
@ -881,7 +870,7 @@ const restoreUserData = function(request) {
|
||||||
lastBackupFile: '',
|
lastBackupFile: '',
|
||||||
lastBackupTime: 0
|
lastBackupTime: 0
|
||||||
});
|
});
|
||||||
µb.assets.put(µb.userFiltersPath, userData.userFilters);
|
µb.saveUserFilters(userData.userFilters);
|
||||||
if ( Array.isArray(userData.selectedFilterLists) ) {
|
if ( Array.isArray(userData.selectedFilterLists) ) {
|
||||||
µb.saveSelectedFilterLists(userData.selectedFilterLists, restart);
|
µb.saveSelectedFilterLists(userData.selectedFilterLists, restart);
|
||||||
} else {
|
} else {
|
||||||
|
@ -902,18 +891,17 @@ const restoreUserData = function(request) {
|
||||||
|
|
||||||
// Remove all stored data but keep global counts, people can become
|
// Remove all stored data but keep global counts, people can become
|
||||||
// quite attached to numbers
|
// quite attached to numbers
|
||||||
const resetUserData = function() {
|
const resetUserData = async function() {
|
||||||
let count = 3;
|
|
||||||
const countdown = ( ) => {
|
|
||||||
count -= 1;
|
|
||||||
if ( count === 0 ) {
|
|
||||||
vAPI.app.restart();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
µb.cacheStorage.clear().then(( ) => countdown()); // 1
|
|
||||||
vAPI.storage.clear(countdown); // 2
|
|
||||||
µb.saveLocalSettings(countdown); // 3
|
|
||||||
vAPI.localStorage.removeItem('immediateHiddenSettings');
|
vAPI.localStorage.removeItem('immediateHiddenSettings');
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
µb.cacheStorage.clear(),
|
||||||
|
vAPI.storage.clear(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await µb.saveLocalSettings();
|
||||||
|
|
||||||
|
vAPI.app.restart();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3rd-party filters
|
// 3rd-party filters
|
||||||
|
@ -932,7 +920,7 @@ const prepListEntries = function(entries) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getLists = function(callback) {
|
const getLists = async function(callback) {
|
||||||
const r = {
|
const r = {
|
||||||
autoUpdate: µb.userSettings.autoUpdate,
|
autoUpdate: µb.userSettings.autoUpdate,
|
||||||
available: null,
|
available: null,
|
||||||
|
@ -946,17 +934,15 @@ const getLists = function(callback) {
|
||||||
parseCosmeticFilters: µb.userSettings.parseAllABPHideFilters,
|
parseCosmeticFilters: µb.userSettings.parseAllABPHideFilters,
|
||||||
userFiltersPath: µb.userFiltersPath
|
userFiltersPath: µb.userFiltersPath
|
||||||
};
|
};
|
||||||
const onMetadataReady = function(entries) {
|
const [ lists, metadata ] = await Promise.all([
|
||||||
r.cache = entries;
|
µb.getAvailableLists(),
|
||||||
prepListEntries(r.cache);
|
µb.assets.metadata(),
|
||||||
callback(r);
|
]);
|
||||||
};
|
|
||||||
const onLists = function(lists) {
|
|
||||||
r.available = lists;
|
r.available = lists;
|
||||||
prepListEntries(r.available);
|
prepListEntries(r.available);
|
||||||
µb.assets.metadata(onMetadataReady);
|
r.cache = metadata;
|
||||||
};
|
prepListEntries(r.cache);
|
||||||
µb.getAvailableLists(onLists);
|
callback(r);
|
||||||
};
|
};
|
||||||
|
|
||||||
// My rules
|
// My rules
|
||||||
|
@ -1060,29 +1046,37 @@ const onMessage = function(request, sender, callback) {
|
||||||
// Async
|
// Async
|
||||||
switch ( request.what ) {
|
switch ( request.what ) {
|
||||||
case 'backupUserData':
|
case 'backupUserData':
|
||||||
return backupUserData(callback);
|
return backupUserData().then(data => {
|
||||||
|
callback(data);
|
||||||
|
});
|
||||||
|
|
||||||
case 'getLists':
|
case 'getLists':
|
||||||
return getLists(callback);
|
return getLists(callback);
|
||||||
|
|
||||||
case 'getLocalData':
|
case 'getLocalData':
|
||||||
return getLocalData(callback);
|
return getLocalData().then(localData => {
|
||||||
|
callback(localData);
|
||||||
|
});
|
||||||
|
|
||||||
case 'getShortcuts':
|
case 'getShortcuts':
|
||||||
return getShortcuts(callback);
|
return getShortcuts(callback);
|
||||||
|
|
||||||
case 'readUserFilters':
|
case 'readUserFilters':
|
||||||
return µb.loadUserFilters(callback);
|
return µb.loadUserFilters().then(result => {
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
|
||||||
case 'writeUserFilters':
|
case 'writeUserFilters':
|
||||||
return µb.saveUserFilters(request.content, callback);
|
return µb.saveUserFilters(request.content).then(result => {
|
||||||
|
callback(result);
|
||||||
|
});
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync
|
// Sync
|
||||||
var response;
|
let response;
|
||||||
|
|
||||||
switch ( request.what ) {
|
switch ( request.what ) {
|
||||||
case 'canUpdateShortcuts':
|
case 'canUpdateShortcuts':
|
||||||
|
|
|
@ -573,11 +573,11 @@ RedirectEngine.prototype.toSelfie = function(path) {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
RedirectEngine.prototype.fromSelfie = function(path) {
|
RedirectEngine.prototype.fromSelfie = async function(path) {
|
||||||
return µBlock.assets.get(`${path}/main`).then(details => {
|
const result = await µBlock.assets.get(`${path}/main`);
|
||||||
let selfie;
|
let selfie;
|
||||||
try {
|
try {
|
||||||
selfie = JSON.parse(details.content);
|
selfie = JSON.parse(result.content);
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
}
|
}
|
||||||
if ( selfie instanceof Object === false ) { return false; }
|
if ( selfie instanceof Object === false ) { return false; }
|
||||||
|
@ -587,7 +587,6 @@ RedirectEngine.prototype.fromSelfie = function(path) {
|
||||||
this.resetCache();
|
this.resetCache();
|
||||||
this.modifyTime = Date.now();
|
this.modifyTime = Date.now();
|
||||||
return true;
|
return true;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -788,13 +787,11 @@ RedirectEngine.prototype.selfieFromResources = function() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
RedirectEngine.prototype.resourcesFromSelfie = function() {
|
RedirectEngine.prototype.resourcesFromSelfie = async function() {
|
||||||
return µBlock.assets.get(
|
const result = await µBlock.assets.get('compiled/redirectEngine/resources');
|
||||||
'compiled/redirectEngine/resources'
|
|
||||||
).then(details => {
|
|
||||||
let selfie;
|
let selfie;
|
||||||
try {
|
try {
|
||||||
selfie = JSON.parse(details.content);
|
selfie = JSON.parse(result.content);
|
||||||
} catch(ex) {
|
} catch(ex) {
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
@ -810,7 +807,6 @@ RedirectEngine.prototype.resourcesFromSelfie = function() {
|
||||||
this.resources.set(token, RedirectEntry.fromSelfie(entry));
|
this.resources.set(token, RedirectEntry.fromSelfie(entry));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
RedirectEngine.prototype.invalidateResourcesSelfie = function() {
|
RedirectEngine.prototype.invalidateResourcesSelfie = function() {
|
||||||
|
|
|
@ -23,59 +23,64 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µBlock.staticFilteringReverseLookup = (function() {
|
µBlock.staticFilteringReverseLookup = (( ) => {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var worker = null;
|
const workerTTL = 5 * 60 * 1000;
|
||||||
var workerTTL = 5 * 60 * 1000;
|
const pendingResponses = new Map();
|
||||||
var workerTTLTimer = null;
|
|
||||||
var needLists = true;
|
let worker = null;
|
||||||
var messageId = 1;
|
let workerTTLTimer;
|
||||||
var pendingResponses = Object.create(null);
|
let needLists = true;
|
||||||
|
let messageId = 1;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onWorkerMessage = function(e) {
|
const onWorkerMessage = function(e) {
|
||||||
var msg = e.data;
|
const msg = e.data;
|
||||||
var callback = pendingResponses[msg.id];
|
const callback = pendingResponses.get(msg.id);
|
||||||
delete pendingResponses[msg.id];
|
pendingResponses.delete(msg.id);
|
||||||
callback(msg.response);
|
callback(msg.response);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var stopWorker = function() {
|
const stopWorker = function() {
|
||||||
workerTTLTimer = null;
|
if ( workerTTLTimer !== undefined ) {
|
||||||
if ( worker === null ) {
|
clearTimeout(workerTTLTimer);
|
||||||
return;
|
workerTTLTimer = undefined;
|
||||||
}
|
}
|
||||||
|
if ( worker === null ) { return; }
|
||||||
worker.terminate();
|
worker.terminate();
|
||||||
worker = null;
|
worker = null;
|
||||||
needLists = true;
|
needLists = true;
|
||||||
pendingResponses = Object.create(null);
|
pendingResponses.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var initWorker = function(callback) {
|
const initWorker = function() {
|
||||||
if ( worker === null ) {
|
if ( worker === null ) {
|
||||||
worker = new Worker('js/reverselookup-worker.js');
|
worker = new Worker('js/reverselookup-worker.js');
|
||||||
worker.onmessage = onWorkerMessage;
|
worker.onmessage = onWorkerMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( needLists === false ) {
|
// The worker will be shutdown after n minutes without being used.
|
||||||
callback();
|
if ( workerTTLTimer !== undefined ) {
|
||||||
return;
|
clearTimeout(workerTTLTimer);
|
||||||
}
|
}
|
||||||
|
workerTTLTimer = vAPI.setTimeout(stopWorker, workerTTL);
|
||||||
|
|
||||||
|
if ( needLists === false ) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
needLists = false;
|
needLists = false;
|
||||||
|
|
||||||
var entries = Object.create(null);
|
const entries = new Map();
|
||||||
var countdown = 0;
|
|
||||||
|
|
||||||
var onListLoaded = function(details) {
|
const onListLoaded = function(details) {
|
||||||
var entry = entries[details.assetKey];
|
const entry = entries.get(details.assetKey);
|
||||||
|
|
||||||
// https://github.com/gorhill/uBlock/issues/536
|
// https://github.com/gorhill/uBlock/issues/536
|
||||||
// Use assetKey when there is no filter list title.
|
// Use assetKey when there is no filter list title.
|
||||||
|
@ -89,44 +94,40 @@ var initWorker = function(callback) {
|
||||||
content: details.content
|
content: details.content
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
countdown -= 1;
|
|
||||||
if ( countdown === 0 ) {
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var µb = µBlock;
|
const µb = µBlock;
|
||||||
var listKey, entry;
|
for ( const listKey in µb.availableFilterLists ) {
|
||||||
|
|
||||||
for ( listKey in µb.availableFilterLists ) {
|
|
||||||
if ( µb.availableFilterLists.hasOwnProperty(listKey) === false ) {
|
if ( µb.availableFilterLists.hasOwnProperty(listKey) === false ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
entry = µb.availableFilterLists[listKey];
|
const entry = µb.availableFilterLists[listKey];
|
||||||
if ( entry.off === true ) { continue; }
|
if ( entry.off === true ) { continue; }
|
||||||
entries[listKey] = {
|
entries.set(listKey, {
|
||||||
title: listKey !== µb.userFiltersPath ?
|
title: listKey !== µb.userFiltersPath ?
|
||||||
entry.title :
|
entry.title :
|
||||||
vAPI.i18n('1pPageName'),
|
vAPI.i18n('1pPageName'),
|
||||||
supportURL: entry.supportURL || ''
|
supportURL: entry.supportURL || ''
|
||||||
};
|
});
|
||||||
countdown += 1;
|
}
|
||||||
|
if ( entries.size === 0 ) {
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( countdown === 0 ) {
|
const promises = [];
|
||||||
callback();
|
for ( const listKey of entries.keys() ) {
|
||||||
return;
|
promises.push(
|
||||||
}
|
µb.getCompiledFilterList(listKey).then(details => {
|
||||||
|
onListLoaded(details);
|
||||||
for ( listKey in entries ) {
|
})
|
||||||
µb.getCompiledFilterList(listKey, onListLoaded);
|
);
|
||||||
}
|
}
|
||||||
|
return Promise.all(promises);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var fromNetFilter = function(compiledFilter, rawFilter, callback) {
|
const fromNetFilter = async function(compiledFilter, rawFilter, callback) {
|
||||||
if ( typeof callback !== 'function' ) {
|
if ( typeof callback !== 'function' ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -136,32 +137,22 @@ var fromNetFilter = function(compiledFilter, rawFilter, callback) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( workerTTLTimer !== null ) {
|
await initWorker();
|
||||||
clearTimeout(workerTTLTimer);
|
|
||||||
workerTTLTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var onWorkerReady = function() {
|
const id = messageId++;
|
||||||
var id = messageId++;
|
const message = {
|
||||||
var message = {
|
|
||||||
what: 'fromNetFilter',
|
what: 'fromNetFilter',
|
||||||
id: id,
|
id: id,
|
||||||
compiledFilter: compiledFilter,
|
compiledFilter: compiledFilter,
|
||||||
rawFilter: rawFilter
|
rawFilter: rawFilter
|
||||||
};
|
};
|
||||||
pendingResponses[id] = callback;
|
pendingResponses.set(id, callback);
|
||||||
worker.postMessage(message);
|
worker.postMessage(message);
|
||||||
|
|
||||||
// The worker will be shutdown after n minutes without being used.
|
|
||||||
workerTTLTimer = vAPI.setTimeout(stopWorker, workerTTL);
|
|
||||||
};
|
|
||||||
|
|
||||||
initWorker(onWorkerReady);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var fromCosmeticFilter = function(details, callback) {
|
const fromCosmeticFilter = async function(details, callback) {
|
||||||
if ( typeof callback !== 'function' ) { return; }
|
if ( typeof callback !== 'function' ) { return; }
|
||||||
|
|
||||||
if ( details.rawFilter === '' ) {
|
if ( details.rawFilter === '' ) {
|
||||||
|
@ -169,15 +160,11 @@ var fromCosmeticFilter = function(details, callback) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( workerTTLTimer !== null ) {
|
await initWorker();
|
||||||
clearTimeout(workerTTLTimer);
|
|
||||||
workerTTLTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let onWorkerReady = function() {
|
const id = messageId++;
|
||||||
let id = messageId++;
|
const hostname = µBlock.URI.hostnameFromURI(details.url);
|
||||||
let hostname = µBlock.URI.hostnameFromURI(details.url);
|
pendingResponses.set(id, callback);
|
||||||
pendingResponses[id] = callback;
|
|
||||||
worker.postMessage({
|
worker.postMessage({
|
||||||
what: 'fromCosmeticFilter',
|
what: 'fromCosmeticFilter',
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -187,32 +174,24 @@ var fromCosmeticFilter = function(details, callback) {
|
||||||
.matchStringGenericHide(details.url) === 2,
|
.matchStringGenericHide(details.url) === 2,
|
||||||
rawFilter: details.rawFilter
|
rawFilter: details.rawFilter
|
||||||
});
|
});
|
||||||
|
|
||||||
// The worker will be shutdown after n minutes without being used.
|
|
||||||
workerTTLTimer = vAPI.setTimeout(stopWorker, workerTTL);
|
|
||||||
};
|
|
||||||
|
|
||||||
initWorker(onWorkerReady);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// This tells the worker that filter lists may have changed.
|
// This tells the worker that filter lists may have changed.
|
||||||
|
|
||||||
var resetLists = function() {
|
const resetLists = function() {
|
||||||
needLists = true;
|
needLists = true;
|
||||||
if ( worker === null ) {
|
if ( worker === null ) { return; }
|
||||||
return;
|
|
||||||
}
|
|
||||||
worker.postMessage({ what: 'resetLists' });
|
worker.postMessage({ what: 'resetLists' });
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fromNetFilter: fromNetFilter,
|
fromNetFilter,
|
||||||
fromCosmeticFilter: fromCosmeticFilter,
|
fromCosmeticFilter,
|
||||||
resetLists: resetLists,
|
resetLists,
|
||||||
shutdown: stopWorker
|
shutdown: stopWorker
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
106
src/js/start.js
106
src/js/start.js
|
@ -25,8 +25,8 @@
|
||||||
|
|
||||||
// Load all: executed once.
|
// Load all: executed once.
|
||||||
|
|
||||||
{
|
(async ( ) => {
|
||||||
// >>>>> start of local scope
|
// >>>>> start of private scope
|
||||||
|
|
||||||
const µb = µBlock;
|
const µb = µBlock;
|
||||||
|
|
||||||
|
@ -97,8 +97,6 @@ const onAllReady = function() {
|
||||||
vAPI.app.restart();
|
vAPI.app.restart();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info(`All ready ${Date.now()-vAPI.T0} ms after launch`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -152,27 +150,6 @@ const initializeTabs = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// Filtering engines dependencies:
|
|
||||||
// - PSL
|
|
||||||
|
|
||||||
const onPSLReady = function() {
|
|
||||||
log.info(`PSL ready ${Date.now()-vAPI.T0} ms after launch`);
|
|
||||||
|
|
||||||
µb.selfieManager.load().then(valid => {
|
|
||||||
if ( valid === true ) {
|
|
||||||
log.info(`Selfie ready ${Date.now()-vAPI.T0} ms after launch`);
|
|
||||||
onAllReady();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
µb.loadFilterLists(( ) => {
|
|
||||||
log.info(`Filter lists ready ${Date.now()-vAPI.T0} ms after launch`);
|
|
||||||
onAllReady();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
const onCommandShortcutsReady = function(commandShortcuts) {
|
const onCommandShortcutsReady = function(commandShortcuts) {
|
||||||
if ( Array.isArray(commandShortcuts) === false ) { return; }
|
if ( Array.isArray(commandShortcuts) === false ) { return; }
|
||||||
µb.commandShortcuts = new Map(commandShortcuts);
|
µb.commandShortcuts = new Map(commandShortcuts);
|
||||||
|
@ -226,8 +203,6 @@ const onNetWhitelistReady = function(netWhitelistRaw) {
|
||||||
// User settings are in memory
|
// User settings are in memory
|
||||||
|
|
||||||
const onUserSettingsReady = function(fetched) {
|
const onUserSettingsReady = function(fetched) {
|
||||||
log.info(`User settings ready ${Date.now()-vAPI.T0} ms after launch`);
|
|
||||||
|
|
||||||
const userSettings = µb.userSettings;
|
const userSettings = µb.userSettings;
|
||||||
|
|
||||||
fromFetch(userSettings, fetched);
|
fromFetch(userSettings, fetched);
|
||||||
|
@ -271,8 +246,6 @@ const onSystemSettingsReady = function(fetched) {
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const onFirstFetchReady = function(fetched) {
|
const onFirstFetchReady = function(fetched) {
|
||||||
log.info(`First fetch ready ${Date.now()-vAPI.T0} ms after launch`);
|
|
||||||
|
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/507
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/507
|
||||||
// Firefox-specific: somehow `fetched` is undefined under certain
|
// Firefox-specific: somehow `fetched` is undefined under certain
|
||||||
// circumstances even though we asked to load with default values.
|
// circumstances even though we asked to load with default values.
|
||||||
|
@ -291,10 +264,6 @@ const onFirstFetchReady = function(fetched) {
|
||||||
onNetWhitelistReady(fetched.netWhitelist);
|
onNetWhitelistReady(fetched.netWhitelist);
|
||||||
onVersionReady(fetched.version);
|
onVersionReady(fetched.version);
|
||||||
onCommandShortcutsReady(fetched.commandShortcuts);
|
onCommandShortcutsReady(fetched.commandShortcuts);
|
||||||
|
|
||||||
µb.loadPublicSuffixList().then(( ) => {
|
|
||||||
onPSLReady();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -347,42 +316,45 @@ const createDefaultProps = function() {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const onHiddenSettingsReady = function() {
|
try {
|
||||||
return µb.cacheStorage.select(
|
// https://github.com/gorhill/uBlock/issues/531
|
||||||
µb.hiddenSettings.cacheStorageAPI
|
await µb.restoreAdminSettings();
|
||||||
).then(backend => {
|
|
||||||
log.info(`Backend storage for cache will be ${backend}`);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/******************************************************************************/
|
|
||||||
|
|
||||||
// TODO(seamless migration):
|
|
||||||
// Eventually selected filter list keys will be loaded as a fetchable
|
|
||||||
// property. Until then we need to handle backward and forward
|
|
||||||
// compatibility, this means a special asynchronous call to load selected
|
|
||||||
// filter lists.
|
|
||||||
|
|
||||||
const onAdminSettingsRestored = function() {
|
|
||||||
log.info(`Admin settings ready ${Date.now()-vAPI.T0} ms after launch`);
|
log.info(`Admin settings ready ${Date.now()-vAPI.T0} ms after launch`);
|
||||||
|
|
||||||
Promise.all([
|
await µb.loadHiddenSettings();
|
||||||
µb.loadHiddenSettings().then(( ) =>
|
log.info(`Hidden settings ready ${Date.now()-vAPI.T0} ms after launch`);
|
||||||
onHiddenSettingsReady()
|
|
||||||
),
|
const cacheBackend = await µb.cacheStorage.select(
|
||||||
µb.loadSelectedFilterLists(),
|
µb.hiddenSettings.cacheStorageAPI
|
||||||
]).then(( ) => {
|
);
|
||||||
|
log.info(`Backend storage for cache will be ${cacheBackend}`);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
µb.loadSelectedFilterLists().then(( ) => {
|
||||||
log.info(`List selection ready ${Date.now()-vAPI.T0} ms after launch`);
|
log.info(`List selection ready ${Date.now()-vAPI.T0} ms after launch`);
|
||||||
vAPI.storage.get(createDefaultProps(), onFirstFetchReady);
|
}),
|
||||||
});
|
vAPI.storage.get(createDefaultProps()).then(fetched => {
|
||||||
};
|
log.info(`First fetch ready ${Date.now()-vAPI.T0} ms after launch`);
|
||||||
|
onFirstFetchReady(fetched);
|
||||||
|
}),
|
||||||
|
µb.loadPublicSuffixList().then(( ) => {
|
||||||
|
log.info(`PSL ready ${Date.now()-vAPI.T0} ms after launch`);
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
/******************************************************************************/
|
const selfieIsValid = await µb.selfieManager.load();
|
||||||
|
if ( selfieIsValid === true ) {
|
||||||
// https://github.com/gorhill/uBlock/issues/531
|
log.info(`Selfie ready ${Date.now()-vAPI.T0} ms after launch`);
|
||||||
µb.restoreAdminSettings().then(( ) => {
|
} else {
|
||||||
onAdminSettingsRestored();
|
await µb.loadFilterLists();
|
||||||
});
|
log.info(`Filter lists ready ${Date.now()-vAPI.T0} ms after launch`);
|
||||||
|
}
|
||||||
// <<<<< end of local scope
|
} catch (ex) {
|
||||||
|
console.trace(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAllReady();
|
||||||
|
log.info(`All ready ${Date.now()-vAPI.T0} ms after launch`);
|
||||||
|
|
||||||
|
// <<<<< end of private scope
|
||||||
|
})();
|
||||||
|
|
|
@ -25,48 +25,44 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µBlock.getBytesInUse = function(callback) {
|
µBlock.getBytesInUse = async function() {
|
||||||
if ( typeof callback !== 'function' ) {
|
const promises = [];
|
||||||
callback = this.noopFunc;
|
|
||||||
}
|
|
||||||
let bytesInUse;
|
let bytesInUse;
|
||||||
let countdown = 0;
|
|
||||||
|
|
||||||
const process = count => {
|
|
||||||
if ( typeof count === 'number' ) {
|
|
||||||
if ( bytesInUse === undefined ) {
|
|
||||||
bytesInUse = 0;
|
|
||||||
}
|
|
||||||
bytesInUse += count;
|
|
||||||
}
|
|
||||||
countdown -= 1;
|
|
||||||
if ( countdown > 0 ) { return; }
|
|
||||||
µBlock.storageUsed = bytesInUse;
|
|
||||||
callback(bytesInUse);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Not all platforms implement this method.
|
// Not all platforms implement this method.
|
||||||
if ( vAPI.storage.getBytesInUse instanceof Function ) {
|
promises.push(
|
||||||
countdown += 1;
|
vAPI.storage.getBytesInUse instanceof Function
|
||||||
vAPI.storage.getBytesInUse(null, process);
|
? vAPI.storage.getBytesInUse(null)
|
||||||
}
|
: undefined
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
navigator.storage instanceof Object &&
|
navigator.storage instanceof Object &&
|
||||||
navigator.storage.estimate instanceof Function
|
navigator.storage.estimate instanceof Function
|
||||||
) {
|
) {
|
||||||
countdown += 1;
|
promises.push(navigator.storage.estimate());
|
||||||
navigator.storage.estimate().then(estimate => {
|
|
||||||
process(estimate.usage);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if ( countdown === 0 ) {
|
|
||||||
callback();
|
const results = await Promise.all(promises);
|
||||||
|
|
||||||
|
const processCount = count => {
|
||||||
|
if ( typeof count !== 'number' ) { return; }
|
||||||
|
if ( bytesInUse === undefined ) { bytesInUse = 0; }
|
||||||
|
bytesInUse += count;
|
||||||
|
return bytesInUse;
|
||||||
|
};
|
||||||
|
|
||||||
|
processCount(results[0]);
|
||||||
|
if ( results.length > 1 && results[1] instanceof Object ) {
|
||||||
|
processCount(results[1].usage);
|
||||||
}
|
}
|
||||||
|
µBlock.storageUsed = bytesInUse;
|
||||||
|
return bytesInUse;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µBlock.saveLocalSettings = (function() {
|
µBlock.saveLocalSettings = (( ) => {
|
||||||
const saveAfter = 4 * 60 * 1000;
|
const saveAfter = 4 * 60 * 1000;
|
||||||
|
|
||||||
const onTimeout = ( ) => {
|
const onTimeout = ( ) => {
|
||||||
|
@ -79,9 +75,9 @@
|
||||||
|
|
||||||
vAPI.setTimeout(onTimeout, saveAfter);
|
vAPI.setTimeout(onTimeout, saveAfter);
|
||||||
|
|
||||||
return function(callback) {
|
return function() {
|
||||||
this.localSettingsLastSaved = Date.now();
|
this.localSettingsLastSaved = Date.now();
|
||||||
vAPI.storage.set(this.localSettings, callback);
|
return vAPI.storage.set(this.localSettings);
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
@ -93,14 +89,10 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µBlock.loadHiddenSettings = function() {
|
µBlock.loadHiddenSettings = async function() {
|
||||||
return new Promise(resolve => {
|
const bin = await vAPI.storage.get('hiddenSettings');
|
||||||
// >>>> start of executor
|
if ( bin instanceof Object === false ) { return; }
|
||||||
|
|
||||||
vAPI.storage.get('hiddenSettings', bin => {
|
|
||||||
if ( bin instanceof Object === false ) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
const hs = bin.hiddenSettings;
|
const hs = bin.hiddenSettings;
|
||||||
if ( hs instanceof Object ) {
|
if ( hs instanceof Object ) {
|
||||||
const hsDefault = this.hiddenSettingsDefault;
|
const hsDefault = this.hiddenSettingsDefault;
|
||||||
|
@ -121,12 +113,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.log.verbosity = this.hiddenSettings.consoleLogLevel;
|
self.log.verbosity = this.hiddenSettings.consoleLogLevel;
|
||||||
resolve();
|
|
||||||
this.fireDOMEvent('hiddenSettingsChanged');
|
this.fireDOMEvent('hiddenSettingsChanged');
|
||||||
});
|
|
||||||
|
|
||||||
// <<<< end of executor
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Note: Save only the settings which values differ from the default ones.
|
// Note: Save only the settings which values differ from the default ones.
|
||||||
|
@ -261,30 +248,16 @@
|
||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
µBlock.loadSelectedFilterLists = function() {
|
µBlock.loadSelectedFilterLists = async function() {
|
||||||
return new Promise(resolve => {
|
const bin = await vAPI.storage.get('selectedFilterLists');
|
||||||
// >>>> start of executor
|
if ( bin instanceof Object && Array.isArray(bin.selectedFilterLists) ) {
|
||||||
|
this.selectedFilterLists = bin.selectedFilterLists;
|
||||||
vAPI.storage.get('selectedFilterLists', bin => {
|
|
||||||
// Select default filter lists if first-time launch.
|
|
||||||
if (
|
|
||||||
bin instanceof Object === false ||
|
|
||||||
Array.isArray(bin.selectedFilterLists) === false
|
|
||||||
) {
|
|
||||||
this.assets.metadata(availableLists => {
|
|
||||||
this.saveSelectedFilterLists(
|
|
||||||
this.autoSelectRegionalFilterLists(availableLists)
|
|
||||||
);
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.selectedFilterLists = bin.selectedFilterLists;
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
// <<<< end of executor
|
// Select default filter lists if first-time launch.
|
||||||
});
|
const lists = await this.assets.metadata();
|
||||||
|
this.saveSelectedFilterLists(this.autoSelectRegionalFilterLists(lists));
|
||||||
};
|
};
|
||||||
|
|
||||||
µBlock.saveSelectedFilterLists = function(newKeys, append, callback) {
|
µBlock.saveSelectedFilterLists = function(newKeys, append, callback) {
|
||||||
|
@ -409,20 +382,20 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µBlock.saveUserFilters = function(content, callback) {
|
µBlock.saveUserFilters = function(content) {
|
||||||
// https://github.com/gorhill/uBlock/issues/1022
|
// https://github.com/gorhill/uBlock/issues/1022
|
||||||
// Be sure to end with an empty line.
|
// Be sure to end with an empty line.
|
||||||
content = content.trim();
|
content = content.trim();
|
||||||
if ( content !== '' ) { content += '\n'; }
|
if ( content !== '' ) { content += '\n'; }
|
||||||
this.assets.put(this.userFiltersPath, content, callback);
|
|
||||||
this.removeCompiledFilterList(this.userFiltersPath);
|
this.removeCompiledFilterList(this.userFiltersPath);
|
||||||
|
return this.assets.put(this.userFiltersPath, content);
|
||||||
};
|
};
|
||||||
|
|
||||||
µBlock.loadUserFilters = function(callback) {
|
µBlock.loadUserFilters = function() {
|
||||||
return this.assets.get(this.userFiltersPath, callback);
|
return this.assets.get(this.userFiltersPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
µBlock.appendUserFilters = function(filters, options) {
|
µBlock.appendUserFilters = async function(filters, options) {
|
||||||
filters = filters.trim();
|
filters = filters.trim();
|
||||||
if ( filters.length === 0 ) { return; }
|
if ( filters.length === 0 ) { return; }
|
||||||
|
|
||||||
|
@ -443,7 +416,27 @@
|
||||||
.replace('{{origin}}', options.origin);
|
.replace('{{origin}}', options.origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSaved = ( ) => {
|
const details = await this.loadUserFilters();
|
||||||
|
if ( details.error ) { return; }
|
||||||
|
|
||||||
|
// The comment, if any, will be applied if and only if it is different
|
||||||
|
// from the last comment found in the user filter list.
|
||||||
|
if ( comment !== '' ) {
|
||||||
|
const pos = details.content.lastIndexOf(comment);
|
||||||
|
if (
|
||||||
|
pos === -1 ||
|
||||||
|
details.content.indexOf('\n!', pos + 1) !== -1
|
||||||
|
) {
|
||||||
|
filters = '\n' + comment + '\n' + filters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/chrisaljoudi/uBlock/issues/976
|
||||||
|
// If we reached this point, the filter quite probably needs to be
|
||||||
|
// added for sure: do not try to be too smart, trying to avoid
|
||||||
|
// duplicates at this point may lead to more issues.
|
||||||
|
await this.saveUserFilters(details.content.trim() + '\n' + filters);
|
||||||
|
|
||||||
const compiledFilters = this.compileFilters(
|
const compiledFilters = this.compileFilters(
|
||||||
filters,
|
filters,
|
||||||
{ assetKey: this.userFiltersPath }
|
{ assetKey: this.userFiltersPath }
|
||||||
|
@ -473,29 +466,6 @@
|
||||||
if ( options.killCache ) {
|
if ( options.killCache ) {
|
||||||
browser.webRequest.handlerBehaviorChanged();
|
browser.webRequest.handlerBehaviorChanged();
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const onLoaded = details => {
|
|
||||||
if ( details.error ) { return; }
|
|
||||||
// The comment, if any, will be applied if and only if it is different
|
|
||||||
// from the last comment found in the user filter list.
|
|
||||||
if ( comment !== '' ) {
|
|
||||||
const pos = details.content.lastIndexOf(comment);
|
|
||||||
if (
|
|
||||||
pos === -1 ||
|
|
||||||
details.content.indexOf('\n!', pos + 1) !== -1
|
|
||||||
) {
|
|
||||||
filters = '\n' + comment + '\n' + filters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/976
|
|
||||||
// If we reached this point, the filter quite probably needs to be
|
|
||||||
// added for sure: do not try to be too smart, trying to avoid
|
|
||||||
// duplicates at this point may lead to more issues.
|
|
||||||
this.saveUserFilters(details.content.trim() + '\n' + filters, onSaved);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.loadUserFilters(onLoaded);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
µBlock.createUserFilters = function(details) {
|
µBlock.createUserFilters = function(details) {
|
||||||
|
@ -525,7 +495,7 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µBlock.getAvailableLists = function(callback) {
|
µBlock.getAvailableLists = async function() {
|
||||||
let oldAvailableLists = {},
|
let oldAvailableLists = {},
|
||||||
newAvailableLists = {};
|
newAvailableLists = {};
|
||||||
|
|
||||||
|
@ -577,10 +547,34 @@
|
||||||
this.saveSelectedFilterLists([ listURL ], true);
|
this.saveSelectedFilterLists([ listURL ], true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Load previously saved available lists -- these contains data
|
||||||
|
// computed at run-time, we will reuse this data if possible.
|
||||||
|
const [ bin, entries ] = await Promise.all([
|
||||||
|
vAPI.storage.get('availableFilterLists'),
|
||||||
|
this.assets.metadata(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
oldAvailableLists = bin && bin.availableFilterLists || {};
|
||||||
|
|
||||||
|
for ( const assetKey in entries ) {
|
||||||
|
if ( entries.hasOwnProperty(assetKey) === false ) { continue; }
|
||||||
|
const entry = entries[assetKey];
|
||||||
|
if ( entry.content !== 'filters' ) { continue; }
|
||||||
|
newAvailableLists[assetKey] = Object.assign({}, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load set of currently selected filter lists.
|
||||||
|
const listKeySet = new Set(this.selectedFilterLists);
|
||||||
|
for ( const listKey in newAvailableLists ) {
|
||||||
|
if ( newAvailableLists.hasOwnProperty(listKey) ) {
|
||||||
|
newAvailableLists[listKey].off = !listKeySet.has(listKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//finalize();
|
||||||
// Final steps:
|
// Final steps:
|
||||||
// - reuse existing list metadata if any;
|
// - reuse existing list metadata if any;
|
||||||
// - unregister unreferenced imported filter lists if any.
|
// - unregister unreferenced imported filter lists if any.
|
||||||
const finalize = ( ) => {
|
|
||||||
// Reuse existing metadata.
|
// Reuse existing metadata.
|
||||||
for ( const assetKey in oldAvailableLists ) {
|
for ( const assetKey in oldAvailableLists ) {
|
||||||
const oldEntry = oldAvailableLists[assetKey];
|
const oldEntry = oldAvailableLists[assetKey];
|
||||||
|
@ -615,67 +609,25 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove unreferenced imported filter lists.
|
// Remove unreferenced imported filter lists.
|
||||||
const dict = new Set(importedListKeys);
|
|
||||||
for ( const assetKey in newAvailableLists ) {
|
for ( const assetKey in newAvailableLists ) {
|
||||||
const newEntry = newAvailableLists[assetKey];
|
const newEntry = newAvailableLists[assetKey];
|
||||||
if ( newEntry.submitter !== 'user' ) { continue; }
|
if ( newEntry.submitter !== 'user' ) { continue; }
|
||||||
if ( dict.has(assetKey) ) { continue; }
|
if ( importedListKeys.indexOf(assetKey) !== -1 ) { continue; }
|
||||||
delete newAvailableLists[assetKey];
|
delete newAvailableLists[assetKey];
|
||||||
this.assets.unregisterAssetSource(assetKey);
|
this.assets.unregisterAssetSource(assetKey);
|
||||||
this.removeFilterList(assetKey);
|
this.removeFilterList(assetKey);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Built-in filter lists loaded.
|
return newAvailableLists;
|
||||||
const onBuiltinListsLoaded = entries => {
|
|
||||||
for ( const assetKey in entries ) {
|
|
||||||
if ( entries.hasOwnProperty(assetKey) === false ) { continue; }
|
|
||||||
const entry = entries[assetKey];
|
|
||||||
if ( entry.content !== 'filters' ) { continue; }
|
|
||||||
newAvailableLists[assetKey] = Object.assign({}, entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load set of currently selected filter lists.
|
|
||||||
const listKeySet = new Set(this.selectedFilterLists);
|
|
||||||
for ( const listKey in newAvailableLists ) {
|
|
||||||
if ( newAvailableLists.hasOwnProperty(listKey) ) {
|
|
||||||
newAvailableLists[listKey].off = !listKeySet.has(listKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
finalize();
|
|
||||||
callback(newAvailableLists);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Available lists previously computed.
|
|
||||||
const onOldAvailableListsLoaded = bin => {
|
|
||||||
oldAvailableLists = bin && bin.availableFilterLists || {};
|
|
||||||
this.assets.metadata(onBuiltinListsLoaded);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load previously saved available lists -- these contains data
|
|
||||||
// computed at run-time, we will reuse this data if possible.
|
|
||||||
vAPI.storage.get('availableFilterLists', onOldAvailableListsLoaded);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// This is used to be re-entrancy resistant.
|
µBlock.loadFilterLists = (( ) => {
|
||||||
µBlock.loadingFilterLists = false;
|
|
||||||
|
|
||||||
µBlock.loadFilterLists = function(callback) {
|
|
||||||
// Callers are expected to check this first.
|
|
||||||
if ( this.loadingFilterLists ) { return; }
|
|
||||||
this.loadingFilterLists = true;
|
|
||||||
|
|
||||||
const loadedListKeys = [];
|
const loadedListKeys = [];
|
||||||
let filterlistsCount = 0;
|
let loadingPromise;
|
||||||
|
|
||||||
if ( typeof callback !== 'function' ) {
|
const onDone = function() {
|
||||||
callback = this.noopFunc;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDone = ( ) => {
|
|
||||||
this.staticNetFilteringEngine.freeze();
|
this.staticNetFilteringEngine.freeze();
|
||||||
this.staticExtFilteringEngine.freeze();
|
this.staticExtFilteringEngine.freeze();
|
||||||
this.redirectEngine.freeze();
|
this.redirectEngine.freeze();
|
||||||
|
@ -690,15 +642,13 @@
|
||||||
listKeys: loadedListKeys
|
listKeys: loadedListKeys
|
||||||
});
|
});
|
||||||
|
|
||||||
callback();
|
|
||||||
|
|
||||||
this.selfieManager.destroy();
|
this.selfieManager.destroy();
|
||||||
this.lz4Codec.relinquish();
|
this.lz4Codec.relinquish();
|
||||||
|
|
||||||
this.loadingFilterLists = false;
|
loadingPromise = undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyCompiledFilters = (assetKey, compiled) => {
|
const applyCompiledFilters = function(assetKey, compiled) {
|
||||||
const snfe = this.staticNetFilteringEngine;
|
const snfe = this.staticNetFilteringEngine;
|
||||||
const sxfe = this.staticExtFilteringEngine;
|
const sxfe = this.staticExtFilteringEngine;
|
||||||
let acceptedCount = snfe.acceptedCount + sxfe.acceptedCount,
|
let acceptedCount = snfe.acceptedCount + sxfe.acceptedCount,
|
||||||
|
@ -714,15 +664,7 @@
|
||||||
loadedListKeys.push(assetKey);
|
loadedListKeys.push(assetKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onCompiledListLoaded = details => {
|
const onFilterListsReady = function(lists) {
|
||||||
applyCompiledFilters(details.assetKey, details.content);
|
|
||||||
filterlistsCount -= 1;
|
|
||||||
if ( filterlistsCount === 0 ) {
|
|
||||||
onDone();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onFilterListsReady = lists => {
|
|
||||||
this.availableFilterLists = lists;
|
this.availableFilterLists = lists;
|
||||||
|
|
||||||
vAPI.net.suspend();
|
vAPI.net.suspend();
|
||||||
|
@ -740,66 +682,71 @@
|
||||||
for ( const assetKey in lists ) {
|
for ( const assetKey in lists ) {
|
||||||
if ( lists.hasOwnProperty(assetKey) === false ) { continue; }
|
if ( lists.hasOwnProperty(assetKey) === false ) { continue; }
|
||||||
if ( lists[assetKey].off ) { continue; }
|
if ( lists[assetKey].off ) { continue; }
|
||||||
toLoad.push(assetKey);
|
|
||||||
}
|
toLoad.push(
|
||||||
filterlistsCount = toLoad.length;
|
this.getCompiledFilterList(assetKey).then(details => {
|
||||||
if ( filterlistsCount === 0 ) {
|
applyCompiledFilters.call(
|
||||||
return onDone();
|
this,
|
||||||
|
details.assetKey,
|
||||||
|
details.content
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let i = toLoad.length;
|
return Promise.all(toLoad);
|
||||||
while ( i-- ) {
|
|
||||||
this.getCompiledFilterList(toLoad[i], onCompiledListLoaded);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getAvailableLists(onFilterListsReady);
|
return function() {
|
||||||
this.loadRedirectResources();
|
if ( loadingPromise instanceof Promise === false ) {
|
||||||
};
|
loadedListKeys.length = 0;
|
||||||
|
loadingPromise = Promise.all([
|
||||||
|
this.getAvailableLists().then(lists => {
|
||||||
|
return onFilterListsReady.call(this, lists);
|
||||||
|
}),
|
||||||
|
this.loadRedirectResources(),
|
||||||
|
]).then(( ) => {
|
||||||
|
onDone.call(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return loadingPromise;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µBlock.getCompiledFilterList = function(assetKey, callback) {
|
µBlock.getCompiledFilterList = async function(assetKey) {
|
||||||
const compiledPath = 'compiled/' + assetKey;
|
const compiledPath = 'compiled/' + assetKey;
|
||||||
let rawContent;
|
|
||||||
|
|
||||||
const onCompiledListLoaded2 = details => {
|
let compiledDetails = await this.assets.get(compiledPath);
|
||||||
if ( details.content === '' ) {
|
if ( compiledDetails.content !== '' ) {
|
||||||
details.content = this.compileFilters(
|
compiledDetails.assetKey = assetKey;
|
||||||
rawContent,
|
return compiledDetails;
|
||||||
{ assetKey: assetKey }
|
|
||||||
);
|
|
||||||
this.assets.put(compiledPath, details.content);
|
|
||||||
}
|
}
|
||||||
rawContent = undefined;
|
|
||||||
details.assetKey = assetKey;
|
|
||||||
callback(details);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onRawListLoaded = details => {
|
const rawDetails = await this.assets.get(assetKey);
|
||||||
if ( details.content === '' ) {
|
// Compiling an empty string results in an empty string.
|
||||||
details.assetKey = assetKey;
|
if ( rawDetails.content === '' ) {
|
||||||
callback(details);
|
rawDetails.assetKey = assetKey;
|
||||||
return;
|
return rawDetails;
|
||||||
}
|
}
|
||||||
this.extractFilterListMetadata(assetKey, details.content);
|
|
||||||
// Fectching the raw content may cause the compiled content to be
|
this.extractFilterListMetadata(assetKey, rawDetails.content);
|
||||||
|
|
||||||
|
// Fetching the raw content may cause the compiled content to be
|
||||||
// generated somewhere else in uBO, hence we try one last time to
|
// generated somewhere else in uBO, hence we try one last time to
|
||||||
// fetch the compiled content in case it has become available.
|
// fetch the compiled content in case it has become available.
|
||||||
rawContent = details.content;
|
compiledDetails = await this.assets.get(compiledPath);
|
||||||
this.assets.get(compiledPath, onCompiledListLoaded2);
|
if ( compiledDetails.content === '' ) {
|
||||||
};
|
compiledDetails.content = this.compileFilters(
|
||||||
|
rawDetails.content,
|
||||||
const onCompiledListLoaded1 = details => {
|
{ assetKey: assetKey }
|
||||||
if ( details.content === '' ) {
|
);
|
||||||
this.assets.get(assetKey, onRawListLoaded);
|
this.assets.put(compiledPath, compiledDetails.content);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
details.assetKey = assetKey;
|
|
||||||
callback(details);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.assets.get(compiledPath, onCompiledListLoaded1);
|
compiledDetails.assetKey = assetKey;
|
||||||
|
return compiledDetails;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -851,7 +798,7 @@
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µBlock.compileFilters = function(rawText, details) {
|
µBlock.compileFilters = function(rawText, details) {
|
||||||
let writer = new this.CompiledLineIO.Writer();
|
const writer = new this.CompiledLineIO.Writer();
|
||||||
|
|
||||||
// Populate the writer with information potentially useful to the
|
// Populate the writer with information potentially useful to the
|
||||||
// client compilers.
|
// client compilers.
|
||||||
|
@ -1007,8 +954,9 @@
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µBlock.loadRedirectResources = function() {
|
µBlock.loadRedirectResources = async function() {
|
||||||
return this.redirectEngine.resourcesFromSelfie().then(success => {
|
try {
|
||||||
|
const success = await this.redirectEngine.resourcesFromSelfie();
|
||||||
if ( success === true ) { return true; }
|
if ( success === true ) { return true; }
|
||||||
|
|
||||||
const fetchPromises = [
|
const fetchPromises = [
|
||||||
|
@ -1022,8 +970,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(fetchPromises);
|
const results = await Promise.all(fetchPromises);
|
||||||
}).then(results => {
|
|
||||||
if ( Array.isArray(results) === false ) { return results; }
|
if ( Array.isArray(results) === false ) { return results; }
|
||||||
|
|
||||||
let content = '';
|
let content = '';
|
||||||
|
@ -1041,35 +988,34 @@
|
||||||
|
|
||||||
this.redirectEngine.resourcesFromString(content);
|
this.redirectEngine.resourcesFromString(content);
|
||||||
this.redirectEngine.selfieFromResources();
|
this.redirectEngine.selfieFromResources();
|
||||||
return true;
|
} catch(ex) {
|
||||||
}).catch(reason => {
|
log.info(ex);
|
||||||
log.info(reason);
|
|
||||||
return false;
|
return false;
|
||||||
});
|
}
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
µBlock.loadPublicSuffixList = function() {
|
µBlock.loadPublicSuffixList = async function() {
|
||||||
if ( this.hiddenSettings.disableWebAssembly === false ) {
|
if ( this.hiddenSettings.disableWebAssembly === false ) {
|
||||||
publicSuffixList.enableWASM();
|
publicSuffixList.enableWASM();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.assets.get(
|
try {
|
||||||
'compiled/' + this.pslAssetKey
|
const result = await this.assets.get(`compiled/${this.pslAssetKey}`);
|
||||||
).then(details =>
|
if ( publicSuffixList.fromSelfie(result.content, this.base64) ) {
|
||||||
publicSuffixList.fromSelfie(details.content, µBlock.base64)
|
return;
|
||||||
).catch(reason => {
|
}
|
||||||
console.info(reason);
|
} catch (ex) {
|
||||||
return false;
|
console.error(ex);
|
||||||
}).then(success => {
|
return;
|
||||||
if ( success ) { return; }
|
}
|
||||||
return this.assets.get(this.pslAssetKey, details => {
|
|
||||||
if ( details.content !== '' ) {
|
const result = await this.assets.get(this.pslAssetKey);
|
||||||
this.compilePublicSuffixList(details.content);
|
if ( result.content !== '' ) {
|
||||||
|
this.compilePublicSuffixList(result.content);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
µBlock.compilePublicSuffixList = function(content) {
|
µBlock.compilePublicSuffixList = function(content) {
|
||||||
|
@ -1111,7 +1057,7 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const load = function() {
|
const load = async function() {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
µb.assets.get('selfie/main').then(details => {
|
µb.assets.get('selfie/main').then(details => {
|
||||||
if (
|
if (
|
||||||
|
@ -1162,10 +1108,7 @@
|
||||||
}, µb.hiddenSettings.selfieAfter * 60000);
|
}, µb.hiddenSettings.selfieAfter * 60000);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return { load, destroy };
|
||||||
load: load,
|
|
||||||
destroy: destroy
|
|
||||||
};
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
@ -1177,27 +1120,18 @@
|
||||||
// necessarily present, i.e. administrators may removed entries which
|
// necessarily present, i.e. administrators may removed entries which
|
||||||
// values are left to the user's choice.
|
// values are left to the user's choice.
|
||||||
|
|
||||||
µBlock.restoreAdminSettings = function() {
|
µBlock.restoreAdminSettings = async function() {
|
||||||
return new Promise(resolve => {
|
|
||||||
// >>>> start of executor
|
|
||||||
|
|
||||||
if ( vAPI.adminStorage instanceof Object === false ) {
|
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
vAPI.adminStorage.getItem('adminSettings', json => {
|
|
||||||
let data;
|
let data;
|
||||||
if ( typeof json === 'string' && json !== '' ) {
|
|
||||||
try {
|
try {
|
||||||
|
const json = await vAPI.adminStorage.getItem('adminSettings');
|
||||||
|
if ( typeof json === 'string' && json !== '' ) {
|
||||||
data = JSON.parse(json);
|
data = JSON.parse(json);
|
||||||
|
}
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.error(ex);
|
console.error(ex);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if ( data instanceof Object === false ) {
|
if ( data instanceof Object === false ) { return; }
|
||||||
return resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
const bin = {};
|
const bin = {};
|
||||||
let binNotEmpty = false;
|
let binNotEmpty = false;
|
||||||
|
@ -1260,14 +1194,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( typeof data.userFilters === 'string' ) {
|
if ( typeof data.userFilters === 'string' ) {
|
||||||
this.assets.put(this.userFiltersPath, data.userFilters);
|
this.saveUserFilters(data.userFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
// <<<< end of executor
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
Loading…
Reference in a new issue