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:
Raymond Hill 2019-09-15 07:58:28 -04:00
parent 6c7d3a40d6
commit e27328f931
No known key found for this signature in database
GPG key ID: 25E1490B761470C2
7 changed files with 1032 additions and 1207 deletions

View file

@ -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();
}
}
};
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/

View file

@ -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() {

View file

@ -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':

View file

@ -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() {

View file

@ -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
}; };

View file

@ -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
})();

View file

@ -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
});
}; };
/******************************************************************************/ /******************************************************************************/