1
0
mirror of https://github.com/gorhill/uBlock.git synced 2024-11-17 16:02:33 +01:00
uBlock/src/js/assets.js

1063 lines
33 KiB
JavaScript
Raw Normal View History

2014-06-24 00:42:43 +02:00
/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2014-present Raymond Hill
2014-06-24 00:42:43 +02:00
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
'use strict';
2014-06-24 00:42:43 +02:00
/******************************************************************************/
µBlock.assets = (( ) => {
2014-06-24 00:42:43 +02:00
/******************************************************************************/
const reIsExternalPath = /^(?:[a-z-]+):\/\//,
reIsUserAsset = /^user-/,
errorCantConnectTo = vAPI.i18n('errorCantConnectTo'),
noopfunc = function(){};
2014-09-08 23:46:58 +02:00
const api = {};
/******************************************************************************/
const observers = [];
2014-06-24 00:42:43 +02:00
api.addObserver = function(observer) {
if ( observers.indexOf(observer) === -1 ) {
observers.push(observer);
}
2014-12-20 21:28:16 +01:00
};
api.removeObserver = function(observer) {
let pos;
while ( (pos = observers.indexOf(observer)) !== -1 ) {
observers.splice(pos, 1);
}
2014-12-20 21:28:16 +01:00
};
const fireNotification = function(topic, details) {
let result;
for ( const observer of observers ) {
const r = observer(topic, details);
if ( r !== undefined ) { result = r; }
}
return result;
};
/******************************************************************************/
2017-03-05 18:54:47 +01:00
api.fetchText = function(url, onLoad, onError) {
const isExternal = reIsExternalPath.test(url);
let actualUrl = isExternal ? url : vAPI.getURL(url);
2017-05-08 17:49:48 +02:00
// https://github.com/gorhill/uBlock/issues/2592
// Force browser cache to be bypassed, but only for resources which have
// been fetched more than one hour ago.
2017-05-08 17:49:48 +02:00
if ( isExternal ) {
const queryValue = `_=${Math.floor(Date.now() / 3600000) % 12}`;
2017-05-08 17:49:48 +02:00
if ( actualUrl.indexOf('?') === -1 ) {
2017-05-08 18:12:56 +02:00
actualUrl += '?';
2017-05-08 17:49:48 +02:00
} else {
2017-05-08 18:12:56 +02:00
actualUrl += '&';
2017-05-08 17:49:48 +02:00
}
2017-05-08 18:12:56 +02:00
actualUrl += queryValue;
2017-05-08 17:49:48 +02:00
}
2015-02-24 21:35:32 +01:00
2015-10-14 16:28:37 +02:00
if ( typeof onError !== 'function' ) {
onError = onLoad;
}
return new Promise(resolve => {
// Start of executor
const timeoutAfter = µBlock.hiddenSettings.assetFetchTimeout * 1000 || 30000;
const xhr = new XMLHttpRequest();
let contentLoaded = 0;
let timeoutTimer;
const cleanup = function() {
xhr.removeEventListener('load', onLoadEvent);
xhr.removeEventListener('error', onErrorEvent);
xhr.removeEventListener('abort', onErrorEvent);
xhr.removeEventListener('progress', onProgressEvent);
if ( timeoutTimer !== undefined ) {
clearTimeout(timeoutTimer);
timeoutTimer = undefined;
}
};
const onResolve = function(details) {
if ( onLoad instanceof Function ) {
return onLoad(details);
}
resolve(details);
};
const onReject = function(details) {
if ( onError instanceof Function ) {
return onError(details);
}
resolve(details);
};
// https://github.com/gorhill/uMatrix/issues/15
const onLoadEvent = function() {
cleanup();
2015-02-24 00:31:29 +01:00
// xhr for local files gives status 0, but actually succeeds
const details = {
url,
2017-03-05 18:54:47 +01:00
content: '',
statusCode: this.status || 200,
statusText: this.statusText || ''
};
if ( details.statusCode < 200 || details.statusCode >= 300 ) {
return onReject(details);
2015-02-15 13:16:31 +01:00
}
2015-02-24 00:31:29 +01:00
// consider an empty result to be an error
if ( stringIsNotEmpty(this.responseText) === false ) {
return onReject(details);
2015-02-15 13:16:31 +01:00
}
// we never download anything else than plain text: discard if response
2015-02-24 00:31:29 +01:00
// appears to be a HTML document: could happen when server serves
2015-02-15 13:16:31 +01:00
// some kind of error page I suppose
const text = this.responseText.trim();
if ( text.startsWith('<') && text.endsWith('>') ) {
return onReject(details);
}
2017-03-05 18:54:47 +01:00
details.content = this.responseText;
onResolve(details);
};
2015-02-24 21:35:32 +01:00
const onErrorEvent = function() {
cleanup();
µBlock.logger.writeOne({
realm: 'message',
type: 'error',
text: errorCantConnectTo.replace('{{msg}}', actualUrl)
});
onReject({ url, content: '' });
};
const onTimeout = function() {
xhr.abort();
};
// https://github.com/gorhill/uBlock/issues/2526
// - Timeout only when there is no progress.
const onProgressEvent = function(ev) {
if ( ev.loaded === contentLoaded ) { return; }
contentLoaded = ev.loaded;
if ( timeoutTimer !== undefined ) {
clearTimeout(timeoutTimer);
}
timeoutTimer = vAPI.setTimeout(onTimeout, timeoutAfter);
2015-02-24 00:31:29 +01:00
};
2015-02-24 21:35:32 +01:00
// Be ready for thrown exceptions:
// I am pretty sure it used to work, but now using a URL such as
// `file:///` on Chromium 40 results in an exception being thrown.
2015-02-24 00:31:29 +01:00
try {
2017-03-05 18:54:47 +01:00
xhr.open('get', actualUrl, true);
xhr.addEventListener('load', onLoadEvent);
xhr.addEventListener('error', onErrorEvent);
xhr.addEventListener('abort', onErrorEvent);
xhr.addEventListener('progress', onProgressEvent);
2015-02-24 00:31:29 +01:00
xhr.responseType = 'text';
xhr.send();
timeoutTimer = vAPI.setTimeout(onTimeout, timeoutAfter);
2015-02-24 00:31:29 +01:00
} catch (e) {
onErrorEvent.call(xhr);
2015-02-24 00:31:29 +01:00
}
// End of executor
});
2014-06-24 00:42:43 +02:00
};
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/3331
// Support the seamless loading of sublists.
api.fetchFilterList = function(mainlistURL, onLoad, onError) {
const content = [];
const pendingSublistURLs = new Set([ mainlistURL ]);
const loadedSublistURLs = new Set();
const toParsedURL = api.fetchFilterList.toParsedURL;
// https://github.com/NanoAdblocker/NanoCore/issues/239
// 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
// list (instead of the URL of the root list).
const rootDirectoryURL = toParsedURL(mainlistURL);
if ( rootDirectoryURL !== undefined ) {
const pos = rootDirectoryURL.pathname.lastIndexOf('/');
if ( pos !== -1 ) {
rootDirectoryURL.pathname =
rootDirectoryURL.pathname.slice(0, pos + 1);
}
}
let errored = false;
const processIncludeDirectives = function(details) {
const reInclude = /^!#include +(\S+)/gm;
const out = [];
const content = details.content;
let lastIndex = 0;
for (;;) {
const match = reInclude.exec(content);
if ( match === null ) { break; }
if ( toParsedURL(match[1]) !== undefined ) { continue; }
if ( match[1].indexOf('..') !== -1 ) { continue; }
const subURL = toParsedURL(details.url);
subURL.pathname = subURL.pathname.replace(/[^/]+$/, match[1]);
if ( subURL.href.startsWith(rootDirectoryURL.href) === false ) {
continue;
}
if ( pendingSublistURLs.has(subURL.href) ) { continue; }
if ( loadedSublistURLs.has(subURL.href) ) { continue; }
pendingSublistURLs.add(subURL.href);
api.fetchText(subURL.href, onLocalLoadSuccess, onLocalLoadError);
out.push(content.slice(lastIndex, match.index).trim(), subURL.href);
lastIndex = reInclude.lastIndex;
}
out.push(lastIndex === 0 ? content : content.slice(lastIndex).trim());
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);
}
}
2018-03-18 18:56:20 +01:00
if ( pendingSublistURLs.size !== 0 ) { return; }
details.url = mainlistURL;
details.content = content.join('\n').trim();
onLoad(details);
};
// https://github.com/AdguardTeam/FiltersRegistry/issues/82
// Not checking for `errored` status was causing repeated notifications
2018-07-06 16:47:51 +02:00
// to the caller. This can happen when more than one out of multiple
// sublists can't be fetched.
const onLocalLoadError = function(details) {
if ( errored ) { return; }
errored = true;
details.url = mainlistURL;
details.content = '';
onError(details);
};
this.fetchText(mainlistURL, onLocalLoadSuccess, onLocalLoadError);
};
api.fetchFilterList.toParsedURL = function(url) {
try {
return new URL(url);
} catch (ex) {
}
};
/*******************************************************************************
2014-07-22 18:26:11 +02:00
The purpose of the asset source registry is to keep key detail information
about an asset:
- Where to load it from: this may consist of one or more URLs, either local
or remote.
- After how many days an asset should be deemed obsolete -- i.e. in need of
an update.
- The origin and type of an asset.
- The last time an asset was registered.
**/
let assetSourceRegistryPromise,
assetSourceRegistry = Object.create(null);
const registerAssetSource = function(assetKey, dict) {
const entry = assetSourceRegistry[assetKey] || {};
for ( const prop in dict ) {
if ( dict.hasOwnProperty(prop) === false ) { continue; }
if ( dict[prop] === undefined ) {
delete entry[prop];
} else {
entry[prop] = dict[prop];
}
2015-01-16 16:57:56 +01:00
}
let contentURL = dict.contentURL;
if ( contentURL !== undefined ) {
if ( typeof contentURL === 'string' ) {
contentURL = entry.contentURL = [ contentURL ];
} else if ( Array.isArray(contentURL) === false ) {
contentURL = entry.contentURL = [];
}
let remoteURLCount = 0;
for ( let i = 0; i < contentURL.length; i++ ) {
if ( reIsExternalPath.test(contentURL[i]) ) {
remoteURLCount += 1;
}
}
entry.hasLocalURL = remoteURLCount !== contentURL.length;
entry.hasRemoteURL = remoteURLCount !== 0;
} else if ( entry.contentURL === undefined ) {
entry.contentURL = [];
2014-12-20 21:28:16 +01:00
}
if ( typeof entry.updateAfter !== 'number' ) {
entry.updateAfter = 5;
}
if ( entry.submitter ) {
entry.submitTime = Date.now(); // To detect stale entries
2014-06-24 00:42:43 +02:00
}
assetSourceRegistry[assetKey] = entry;
};
2014-06-24 00:42:43 +02:00
const unregisterAssetSource = function(assetKey) {
assetCacheRemove(assetKey);
delete assetSourceRegistry[assetKey];
};
2014-07-22 18:26:11 +02:00
const saveAssetSourceRegistry = (function() {
let timer;
const save = function() {
timer = undefined;
µBlock.cacheStorage.set({ assetSourceRegistry: assetSourceRegistry });
2014-06-24 00:42:43 +02:00
};
return function(lazily) {
if ( timer !== undefined ) {
clearTimeout(timer);
}
if ( lazily ) {
timer = vAPI.setTimeout(save, 500);
} else {
save();
}
2014-06-24 00:42:43 +02:00
};
})();
2014-06-24 00:42:43 +02:00
const updateAssetSourceRegistry = function(json, silent) {
let newDict;
try {
newDict = JSON.parse(json);
} catch (ex) {
}
if ( newDict instanceof Object === false ) { return; }
2014-06-24 00:42:43 +02:00
const oldDict = assetSourceRegistry;
2017-01-26 16:17:38 +01:00
// Remove obsolete entries (only those which were built-in).
for ( const assetKey in oldDict ) {
2017-01-26 16:17:38 +01:00
if (
newDict[assetKey] === undefined &&
oldDict[assetKey].submitter === undefined
) {
unregisterAssetSource(assetKey);
2014-07-22 18:26:11 +02:00
}
2017-01-26 16:17:38 +01:00
}
// Add/update existing entries. Notify of new asset sources.
for ( const assetKey in newDict ) {
2017-01-26 16:17:38 +01:00
if ( oldDict[assetKey] === undefined && !silent ) {
fireNotification(
'builtin-asset-source-added',
{ assetKey: assetKey, entry: newDict[assetKey] }
);
}
registerAssetSource(assetKey, newDict[assetKey]);
}
saveAssetSourceRegistry();
2014-06-24 00:42:43 +02:00
};
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;
});
});
}
2014-09-05 22:15:42 +02:00
return assetSourceRegistryPromise;
};
2014-06-24 00:42:43 +02:00
api.registerAssetSource = function(assetKey, details) {
getAssetSourceRegistry().then(( ) => {
registerAssetSource(assetKey, details);
saveAssetSourceRegistry(true);
});
2014-06-24 00:42:43 +02:00
};
api.unregisterAssetSource = function(assetKey) {
getAssetSourceRegistry().then(( ) => {
unregisterAssetSource(assetKey);
saveAssetSourceRegistry(true);
});
};
2014-08-20 15:24:16 +02:00
/*******************************************************************************
The purpose of the asset cache registry is to keep track of all assets
which have been persisted into the local cache.
2014-06-24 00:42:43 +02:00
**/
2014-07-25 22:12:20 +02:00
const assetCacheRegistryStartTime = Date.now();
let assetCacheRegistryPromise;
let assetCacheRegistry = {};
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
const getAssetCacheRegistry = function() {
if ( assetCacheRegistryPromise === undefined ) {
assetCacheRegistryPromise = µBlock.cacheStorage.get(
'assetCacheRegistry'
).then(bin => {
if (
bin instanceof Object &&
bin.assetCacheRegistry instanceof Object
) {
assetCacheRegistry = bin.assetCacheRegistry;
}
});
}
2014-06-24 00:42:43 +02:00
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
return assetCacheRegistryPromise.then(( ) => assetCacheRegistry);
2014-06-24 00:42:43 +02:00
};
const saveAssetCacheRegistry = (function() {
let timer;
const save = function() {
timer = undefined;
µBlock.cacheStorage.set({ assetCacheRegistry });
};
return function(lazily) {
if ( timer !== undefined ) { clearTimeout(timer); }
if ( lazily ) {
timer = vAPI.setTimeout(save, 500);
} else {
save();
}
};
})();
2014-07-25 22:12:20 +02:00
const assetCacheRead = function(assetKey, callback) {
const internalKey = 'cache/' + assetKey;
2014-07-25 22:12:20 +02:00
const reportBack = function(content) {
2018-08-06 18:34:41 +02:00
if ( content instanceof Blob ) { content = ''; }
let details = { assetKey: assetKey, content: content };
2018-08-06 18:34:41 +02:00
if ( content === '' ) { details.error = 'E_NOTFOUND'; }
callback(details);
};
2014-06-24 00:42:43 +02:00
const onAssetRead = function(bin) {
if (
bin instanceof Object === false ||
2018-08-06 18:34:41 +02:00
bin.hasOwnProperty(internalKey) === false
) {
2018-08-06 18:34:41 +02:00
return reportBack('');
}
let entry = assetCacheRegistry[assetKey];
if ( entry === undefined ) {
2018-08-06 18:34:41 +02:00
return reportBack('');
}
entry.readTime = Date.now();
saveAssetCacheRegistry(true);
reportBack(bin[internalKey]);
};
2014-06-24 00:42:43 +02:00
Promise.all([
getAssetCacheRegistry(),
µBlock.cacheStorage.get(internalKey),
]).then(results => {
onAssetRead(results[1]);
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
});
};
2014-06-24 00:42:43 +02:00
const assetCacheWrite = function(assetKey, details, callback) {
2018-08-06 18:34:41 +02:00
let internalKey = 'cache/' + assetKey;
let content = '';
if ( typeof details === 'string' ) {
content = details;
} else if ( details instanceof Object ) {
content = details.content || '';
}
2014-06-24 00:42:43 +02:00
if ( content === '' ) {
return assetCacheRemove(assetKey, callback);
}
const onReady = function() {
2018-08-06 18:34:41 +02:00
let entry = assetCacheRegistry[assetKey];
if ( entry === undefined ) {
entry = assetCacheRegistry[assetKey] = {};
}
entry.writeTime = entry.readTime = Date.now();
if ( details instanceof Object && typeof details.url === 'string' ) {
entry.remoteURL = details.url;
}
µBlock.cacheStorage.set({ assetCacheRegistry, [internalKey]: content });
const result = { assetKey, content };
if ( typeof callback === 'function' ) {
callback(result);
}
// https://github.com/uBlockOrigin/uBlock-issues/issues/248
fireNotification('after-asset-updated', result);
2014-06-24 00:42:43 +02:00
};
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
getAssetCacheRegistry().then(( ) => onReady());
};
2014-06-24 00:42:43 +02:00
const assetCacheRemove = function(pattern, callback) {
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
getAssetCacheRegistry().then(cacheDict => {
const removedEntries = [];
const removedContent = [];
for ( const assetKey in cacheDict ) {
if ( pattern instanceof RegExp && !pattern.test(assetKey) ) {
continue;
}
if ( typeof pattern === 'string' && assetKey !== pattern ) {
continue;
}
removedEntries.push(assetKey);
removedContent.push('cache/' + assetKey);
delete cacheDict[assetKey];
2014-07-20 21:00:26 +02:00
}
if ( removedContent.length !== 0 ) {
µBlock.cacheStorage.remove(removedContent);
µBlock.cacheStorage.set({ assetCacheRegistry });
}
if ( typeof callback === 'function' ) {
callback();
}
for ( let i = 0; i < removedEntries.length; i++ ) {
fireNotification(
'after-asset-updated',
{ assetKey: removedEntries[i] }
);
}
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
});
};
const assetCacheMarkAsDirty = function(pattern, exclude, callback) {
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
if ( typeof exclude === 'function' ) {
callback = exclude;
exclude = undefined;
}
getAssetCacheRegistry().then(cacheDict => {
let mustSave = false;
for ( const assetKey in cacheDict ) {
if ( pattern instanceof RegExp ) {
if ( pattern.test(assetKey) === false ) { continue; }
} else if ( typeof pattern === 'string' ) {
if ( assetKey !== pattern ) { continue; }
} else if ( Array.isArray(pattern) ) {
if ( pattern.indexOf(assetKey) === -1 ) { continue; }
}
if ( exclude instanceof RegExp ) {
if ( exclude.test(assetKey) ) { continue; }
} else if ( typeof exclude === 'string' ) {
if ( assetKey === exclude ) { continue; }
} else if ( Array.isArray(exclude) ) {
if ( exclude.indexOf(assetKey) !== -1 ) { continue; }
}
const cacheEntry = cacheDict[assetKey];
if ( !cacheEntry.writeTime ) { continue; }
cacheDict[assetKey].writeTime = 0;
mustSave = true;
2014-08-21 01:39:49 +02:00
}
if ( mustSave ) {
µBlock.cacheStorage.set({ assetCacheRegistry });
}
if ( typeof callback === 'function' ) {
callback();
2014-07-20 21:00:26 +02:00
}
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
});
};
/******************************************************************************/
const stringIsNotEmpty = function(s) {
return typeof s === 'string' && s !== '';
};
/*******************************************************************************
User assets are NOT persisted in the cache storage. User assets are
recognized by the asset key which always starts with 'user-'.
TODO(seamless migration):
Can remove instances of old user asset keys when I am confident all users
are using uBO v1.11 and beyond.
**/
/*******************************************************************************
User assets are NOT persisted in the cache storage. User assets are
recognized by the asset key which always starts with 'user-'.
**/
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
const readUserAsset = function(assetKey, callback) {
const reportBack = function(content) {
callback({ assetKey, content });
};
vAPI.storage.get(assetKey, bin => {
const content =
bin instanceof Object && typeof bin[assetKey] === 'string'
? bin[assetKey]
: '';
return reportBack(content);
});
// Remove obsolete entry
// TODO: remove once everybody is well beyond 1.18.6
vAPI.storage.remove('assets/user/filters.txt');
};
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
const saveUserAsset = function(assetKey, content, callback) {
vAPI.storage.set({ [assetKey]: content }, ( ) => {
if ( callback instanceof Function ) {
callback({ assetKey, content });
}
});
};
/******************************************************************************/
api.get = function(assetKey, options, callback) {
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 = {};
}
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
return new Promise(resolve => {
// start of executor
if ( assetKey === µBlock.userFiltersPath ) {
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
readUserAsset(assetKey, details => {
callback(details);
resolve(details);
});
return;
}
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
let assetDetails = {},
contentURLs,
contentURL;
const reportBack = (content, err) => {
const details = { assetKey, content };
if ( err ) {
details.error = assetDetails.lastError = err;
} else {
assetDetails.lastError = undefined;
}
if ( options.needSourceURL ) {
if (
contentURL === undefined &&
assetCacheRegistry instanceof Object &&
assetCacheRegistry[assetKey] instanceof Object
) {
details.sourceURL = assetCacheRegistry[assetKey].remoteURL;
}
if ( reIsExternalPath.test(contentURL) ) {
details.sourceURL = contentURL;
}
}
callback(details);
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
resolve(details);
};
const onContentNotLoaded = ( ) => {
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
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);
}
2014-07-20 21:00:26 +02:00
};
const onContentLoaded = details => {
2017-03-05 18:54:47 +01:00
if ( stringIsNotEmpty(details.content) === false ) {
onContentNotLoaded();
return;
}
if ( reIsExternalPath.test(contentURL) && options.dontCache !== true ) {
assetCacheWrite(assetKey, {
2017-03-05 18:54:47 +01:00
content: details.content,
url: contentURL
});
}
2017-03-05 18:54:47 +01:00
reportBack(details.content);
};
const onCachedContentLoaded = details => {
if ( details.content !== '' ) {
return reportBack(details.content);
}
getAssetSourceRegistry().then(registry => {
assetDetails = registry[assetKey] || {};
if ( typeof assetDetails.contentURL === 'string' ) {
contentURLs = [ assetDetails.contentURL ];
} else if ( Array.isArray(assetDetails.contentURL) ) {
contentURLs = assetDetails.contentURL.slice(0);
} else {
contentURLs = [];
}
onContentNotLoaded();
});
};
assetCacheRead(assetKey, onCachedContentLoaded);
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
// end of executor
});
};
/******************************************************************************/
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
const getRemote = function(assetKey, callback) {
let assetDetails = {};
let contentURLs;
let contentURL;
const reportBack = function(content, err) {
const details = { assetKey: assetKey, content: content };
if ( err ) {
details.error = assetDetails.lastError = err;
} else {
assetDetails.lastError = undefined;
}
callback(details);
};
const onRemoteContentLoaded = function(details) {
2017-03-05 18:54:47 +01:00
if ( stringIsNotEmpty(details.content) === false ) {
registerAssetSource(assetKey, { error: { time: Date.now(), error: 'No content' } });
tryLoading();
2015-02-06 18:20:30 +01:00
return;
}
assetCacheWrite(assetKey, {
2017-03-05 18:54:47 +01:00
content: details.content,
url: contentURL
});
registerAssetSource(assetKey, { error: undefined });
2017-03-05 18:54:47 +01:00
reportBack(details.content);
};
const onRemoteContentError = function(details) {
let text = details.statusText;
2017-03-05 18:54:47 +01:00
if ( details.statusCode === 0 ) {
2017-01-22 22:05:16 +01:00
text = 'network error';
}
registerAssetSource(assetKey, { error: { time: Date.now(), error: text } });
tryLoading();
};
const tryLoading = function() {
while ( (contentURL = contentURLs.shift()) ) {
if ( reIsExternalPath.test(contentURL) ) { break; }
2014-07-20 21:00:26 +02:00
}
if ( !contentURL ) {
return reportBack('', 'E_NOTFOUND');
}
if ( assetDetails.content === 'filters' ) {
api.fetchFilterList(contentURL, onRemoteContentLoaded, onRemoteContentError);
} else {
api.fetchText(contentURL, onRemoteContentLoaded, onRemoteContentError);
}
};
2015-02-24 00:31:29 +01:00
getAssetSourceRegistry().then(registry => {
assetDetails = registry[assetKey] || {};
if ( typeof assetDetails.contentURL === 'string' ) {
contentURLs = [ assetDetails.contentURL ];
} else if ( Array.isArray(assetDetails.contentURL) ) {
contentURLs = assetDetails.contentURL.slice(0);
} else {
contentURLs = [];
}
tryLoading();
});
2014-06-24 00:42:43 +02:00
};
2014-08-21 01:39:49 +02:00
/******************************************************************************/
2014-06-24 00:42:43 +02:00
api.put = function(assetKey, content, callback) {
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
return new Promise(resolve => {
const onDone = function(details) {
if ( typeof callback === 'function' ) {
callback(details);
}
resolve(details);
};
if ( reIsUserAsset.test(assetKey) ) {
saveUserAsset(assetKey, content, onDone);
} else {
assetCacheWrite(assetKey, content, onDone);
}
});
};
/******************************************************************************/
api.metadata = function(callback) {
const onReady = function() {
const assetDict = JSON.parse(JSON.stringify(assetSourceRegistry));
const cacheDict = assetCacheRegistry;
const now = Date.now();
for ( const assetKey in assetDict ) {
const assetEntry = assetDict[assetKey];
const cacheEntry = cacheDict[assetKey];
if ( cacheEntry ) {
assetEntry.cached = true;
assetEntry.writeTime = cacheEntry.writeTime;
const obsoleteAfter =
cacheEntry.writeTime + assetEntry.updateAfter * 86400000;
assetEntry.obsolete = obsoleteAfter < now;
assetEntry.remoteURL = cacheEntry.remoteURL;
} else if (
assetEntry.contentURL &&
assetEntry.contentURL.length !== 0
) {
assetEntry.writeTime = 0;
assetEntry.obsolete = true;
}
}
callback(assetDict);
};
Promise.all([
getAssetSourceRegistry(),
getAssetCacheRegistry(),
]).then(
( ) => onReady()
);
};
2014-06-24 00:42:43 +02:00
/******************************************************************************/
Refactor selfie generation into a more flexible persistence mechanism The motivation is to address the higher peak memory usage at launch time with 3rd-gen HNTrie when a selfie was present. The selfie generation prior to this change was to collect all filtering data into a single data structure, and then to serialize that whole structure at once into storage (using JSON.stringify). However, HNTrie serialization requires that a large UintArray32 be converted into a plain JS array, which itslef would be indirectly converted into a JSON string. This was the main reason why peak memory usage would be higher at launch from selfie, since the JSON string would need to be wholly unserialized into JS objects, which themselves would need to be converted into more specialized data structures (like that Uint32Array one). The solution to lower peak memory usage at launch is to refactor selfie generation to allow a more piecemeal approach: each filtering component is given the ability to serialize itself rather than to be forced to be embedded in the master selfie. With this approach, the HNTrie buffer can now serialize to its own storage by converting the buffer data directly into a string which can be directly sent to storage. This avoiding expensive intermediate steps such as converting into a JS array and then to a JSON string. As part of the refactoring, there was also opportunistic code upgrade to ES6 and Promise (eventually all of uBO's code will be proper ES6). Additionally, the polyfill to bring getBytesInUse() to Firefox has been revisited to replace the rather expensive previous implementation with an implementation with virtually no overhead.
2019-02-14 19:33:55 +01:00
api.purge = assetCacheMarkAsDirty;
api.remove = function(pattern, callback) {
assetCacheRemove(pattern, callback);
};
2015-10-14 16:28:37 +02:00
api.rmrf = function() {
assetCacheRemove(/./);
2014-06-24 00:42:43 +02:00
};
/******************************************************************************/
// Asset updater area.
const updaterAssetDelayDefault = 120000;
const updaterUpdated = [];
const updaterFetched = new Set();
let updaterStatus,
updaterTimer,
updaterAssetDelay = updaterAssetDelayDefault;
2015-02-24 00:31:29 +01:00
const updateFirst = function() {
updaterStatus = 'updating';
updaterFetched.clear();
updaterUpdated.length = 0;
fireNotification('before-assets-updated');
updateNext();
2015-02-13 18:10:10 +01:00
};
const updateNext = function() {
let assetDict, cacheDict;
2015-03-11 04:46:18 +01:00
// 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);
2015-02-13 18:10:10 +01:00
}
2015-03-11 16:05:13 +01:00
};
const findOne = function() {
const now = Date.now();
for ( const assetKey in assetDict ) {
const assetEntry = assetDict[assetKey];
if ( assetEntry.hasRemoteURL !== true ) { continue; }
if ( updaterFetched.has(assetKey) ) { continue; }
const cacheEntry = cacheDict[assetKey];
if (
cacheEntry &&
(cacheEntry.writeTime + assetEntry.updateAfter * 86400000) > now
) {
2015-03-11 16:05:13 +01:00
continue;
}
2017-05-08 20:00:41 +02:00
if (
fireNotification(
'before-asset-updated',
{ assetKey: assetKey, type: assetEntry.content }
) === true
2017-05-08 20:00:41 +02:00
) {
return assetKey;
2015-03-11 16:05:13 +01:00
}
garbageCollectOne(assetKey);
2015-03-11 16:05:13 +01:00
}
};
const updatedOne = function(details) {
if ( details.content !== '' ) {
updaterUpdated.push(details.assetKey);
if ( details.assetKey === 'assets.json' ) {
updateAssetSourceRegistry(details.content);
}
2017-01-22 22:05:16 +01:00
} else {
fireNotification('asset-update-failed', { assetKey: details.assetKey });
2015-02-13 18:10:10 +01:00
}
if ( findOne() !== undefined ) {
vAPI.setTimeout(updateNext, updaterAssetDelay);
} else {
updateDone();
2015-02-13 18:10:10 +01:00
}
};
2015-03-11 04:46:18 +01:00
const updateOne = function() {
const assetKey = findOne();
if ( assetKey === undefined ) {
return updateDone();
}
updaterFetched.add(assetKey);
getRemote(assetKey, updatedOne);
};
2015-03-11 04:46:18 +01:00
Promise.all([
getAssetSourceRegistry(),
getAssetCacheRegistry(),
]).then(results => {
assetDict = results[0];
cacheDict = results[1];
updateOne();
});
2015-03-11 04:46:18 +01:00
};
const updateDone = function() {
const assetKeys = updaterUpdated.slice(0);
updaterFetched.clear();
updaterUpdated.length = 0;
updaterStatus = undefined;
updaterAssetDelay = updaterAssetDelayDefault;
fireNotification('after-assets-updated', { assetKeys: assetKeys });
2015-03-11 16:05:13 +01:00
};
api.updateStart = function(details) {
const oldUpdateDelay = updaterAssetDelay;
const newUpdateDelay = typeof details.delay === 'number' ?
details.delay :
updaterAssetDelayDefault;
updaterAssetDelay = Math.min(oldUpdateDelay, newUpdateDelay);
if ( updaterStatus !== undefined ) {
if ( newUpdateDelay < oldUpdateDelay ) {
clearTimeout(updaterTimer);
updaterTimer = vAPI.setTimeout(updateNext, updaterAssetDelay);
2015-02-13 18:10:10 +01:00
}
return;
2015-02-13 18:10:10 +01:00
}
updateFirst();
2015-02-13 18:10:10 +01:00
};
api.updateStop = function() {
if ( updaterTimer ) {
clearTimeout(updaterTimer);
updaterTimer = undefined;
2015-02-24 00:31:29 +01:00
}
if ( updaterStatus !== undefined ) {
updateDone();
2015-02-13 18:10:10 +01:00
}
};
api.isUpdating = function() {
return updaterStatus === 'updating' &&
updaterAssetDelay <= µBlock.hiddenSettings.manualUpdateAssetFetchPeriod;
};
/******************************************************************************/
return api;
/******************************************************************************/
2014-06-24 00:42:43 +02:00
})();
/******************************************************************************/