diff --git a/3p-filters.html b/3p-filters.html index c2706f548..b404fcd78 100644 --- a/3p-filters.html +++ b/3p-filters.html @@ -36,18 +36,59 @@ ul > li > ul > li { .dim { color: #888; } -button.purge { +/* I designed the button with: http://charliepark.org/bootstrap_buttons/ */ +button.reloadAll { + border: 1px solid transparent; + border-radius: 3px; + border-color: #dddddd #dddddd hsl(36, 0%, 85%); + padding: 5px; + background-color: hsl(36, 0%, 72%) !important; + background-repeat: repeat-x; + background-image: linear-gradient(#f2f2f2, #dddddd); + color: #aaa; + } +button.reloadAll.enabled { + border-color: #ffcc7f #ffcc7f hsl(36, 100%, 73%); + color: #222; + background-color: hsl(36, 100%, 75%) !important; + background-image: linear-gradient(#ffdca8, #ffcc7f); + cursor: pointer; + opacity: 0.8; + } +button.reloadAll:hover { + opacity: 1.0; + } +#buttonApply { + display: none; + position: fixed; + right: 1em; + top: 1em; + } +#buttonApply.enabled { + display: initial; + } +span.status { margin: 0; - border: 1px solid #ccc; + border: 1px solid transparent; padding: 1px 2px; + display: inline-block; + font-size: 11px; + opacity: 0.7; +} +span.purge { + border-color: #ddd; color: #444; background-color: #eee; - font-size: 11px; - opacity: 0.6; + cursor: pointer; } -button.purge:hover { +span.purge:hover { opacity: 1; } +span.obsolete { + border-color: hsl(36, 100%, 73%); + color: #222; + background-color: hsl(36, 100%, 75%); + } #externalLists { font-size: smaller; width: 48em; @@ -59,10 +100,11 @@ button.purge:hover {
+Diese Option ermöglicht die Analyse und Anwendung von Adblock Plus-kompatiblen Filtern zum “Verstecken von Elementen” <\/a>. Diese Filter sind in erster Linie kosmetischer Natur und dienen zum Verstecken von Elementen auf einer Webseite, die als optische Belästigung wahrgenommen werden und nicht von den vorhandenen Filtern geblockt werden können.<\/p> Das Aktivieren dieser Option erhöht den Speicherbedarf von µBlock<\/i>.<\/p>",
@@ -159,6 +163,14 @@
"message":"Leere den Cache",
"description":"English: purge cache"
},
+ "3pExternalListNew":{
+ "message":"new version available",
+ "description":"English: new version available"
+ },
+ "3pExternalListObsolete":{
+ "message":"may be obsolete",
+ "description":"English: may be obsolete"
+ },
"1pFormatHint":{
"message":"Eine Regel pro Zeile. Eine Regel kann ein einfacher Host-Name sein oder ein Adblock Plus-kompatibler Filter. Zeilen mit vorangestelltem ‘!’ werden ignoriert.",
"description":"English: One filter per line. A filter can be a plain hostname, or an Adblock Plus-compatible filter. Lines prefixed with ‘!’ will be ignored."
diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 033aa2f14..9d6a101d1 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -99,13 +99,17 @@
"message":"{{used}} used out of {{total}}",
"description":"English: {{used}} used out of {{total}}"
},
- "3pParseAllABPHideFiltersPrompt1":{
- "message":"Parse and enforce Adblock+ element hiding filters.",
- "description":"English: Parse and enforce Adblock+ element hiding filters."
+ "3pAutoUpdatePrompt1":{
+ "message":"Auto-update filter lists.",
+ "description":"English: Auto-update filter lists."
},
- "3pParseAllABPHideFiltersPrompt2":{
- "message":"{{abpHideFilterCount}} element hiding filters used.",
- "description":"English: {{abpHideFilterCount}} element hiding filters used."
+ "3pUpdateNow":{
+ "message":"Update now",
+ "description":"English: Update now"
+ },
+ "3pParseAllABPHideFiltersPrompt1":{
+ "message":"Parse and enforce cosmetic filters.",
+ "description":"English: Parse and enforce Adblock+ element hiding filters."
},
"3pParseAllABPHideFiltersInfo":{
"message":" This option enables the parsing and enforcing of Adblock Plus-compatible “element hiding” filters<\/a>. These filters are essentially cosmetic, they serve to hide elements in a web page which are deemed to be a visual nuisance, and which can't be blocked by the net request-based filtering engine.<\/p> Enabling this feature increases µBlock<\/i>'s memory footprint.<\/p>",
@@ -159,6 +163,14 @@
"message":"purge cache",
"description":"English: purge cache"
},
+ "3pExternalListNew":{
+ "message":"new version available",
+ "description":"English: new version available"
+ },
+ "3pExternalListObsolete":{
+ "message":"may be obsolete",
+ "description":"English: may be obsolete"
+ },
"1pFormatHint":{
"message":"One filter per line. A filter can be a plain hostname, or an Adblock Plus-compatible filter. Lines prefixed with ‘!’ will be ignored.",
"description":"English: One filter per line. A filter can be a plain hostname, or an Adblock Plus-compatible filter. Lines prefixed with ‘!’ will be ignored."
diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json
index 7bb11b7b9..53b9e2b3e 100644
--- a/_locales/fr/messages.json
+++ b/_locales/fr/messages.json
@@ -99,13 +99,17 @@
"message":"{{used}} utilisé(s) sur un total de {{total}}",
"description":"English: {{used}} used out of {{total}}"
},
- "3pParseAllABPHideFiltersPrompt1":{
- "message":"Utiliser en plus les règles Adblock Plus esthétiques",
- "description":"English: Parse and enforce Adblock+ element hiding filters."
+ "3pAutoUpdatePrompt1":{
+ "message":"Mise à jour automatique des listes",
+ "description":"English: Auto-update filter lists."
},
- "3pParseAllABPHideFiltersPrompt2":{
- "message":"{{abpHideFilterCount}} règle(s) Adblock Plus esthétique(s) utilisée(s)",
- "description":"English: {{abpHideFilterCount}} element hiding filters used."
+ "3pUpdateNow":{
+ "message":"Mettre à jour",
+ "description":"English: Update now"
+ },
+ "3pParseAllABPHideFiltersPrompt1":{
+ "message":"Utiliser en plus les règles esthétiques",
+ "description":"English: Parse and enforce cosmetic filters."
},
"3pParseAllABPHideFiltersInfo":{
"message":" Cette option permet de prendre en charge les filtres AdblockPlus de type “element hiding”<\/a>. Ces filtres ont principalement un impact visuel, servant à dissimuler des éléments nuisibles d'une page Web et qui ne sont pas blocables par le filtrage standard.<\/p> L'activation de cette fonctionnalité augmente l'empreinte mémoire de µBlock<\/i><\/p>",
@@ -159,6 +163,14 @@
"message":"vider le cache",
"description":"English: purge cache"
},
+ "3pExternalListNew":{
+ "message":"nouvelle version disponible",
+ "description":"English: new version available"
+ },
+ "3pExternalListObsolete":{
+ "message":"potentiellement désuet",
+ "description":"English: may be obsolete"
+ },
"1pFormatHint":{
"message":"Une règle par ligne. Une règle peut être un simple nom d'hôte, ou encore un filtre respectant la syntaxe des filtres Adblock Plus. Les lignes débutant par ‘!’ seront ignorées.",
"description":"English: One filter per line. A filter can be a plain hostname, or an Adblock Plus-compatible filter. Lines prefixed with ‘!’ will be ignored."
diff --git a/about.html b/about.html
index 26799e53d..eed05b25f 100644
--- a/about.html
+++ b/about.html
@@ -6,46 +6,8 @@
@@ -53,38 +15,14 @@ table td:first-child {
µBlock
-
-
-
-
-
-
-
-
-
- ',
- ' ',
- '',
- path.replace(/^(assets\/[^/]+\/)(.+)$/, '$1$2'),
- '',
- ' ',
- chrome.i18n.getMessage('aboutAssetsUpdateStatus' + status)
- );
- }
- uDom('#assetList table tBody').append(html.join(''));
- uDom('#assetList a').attr('target', '_blank');
- updateList = details.list;
- }
- setAssetListClassBit(0, paths.length !== 0);
- setAssetListClassBit(1, dirty);
- setAssetListClassBit(2, false);
-};
-
-/******************************************************************************/
-
-var updateAssets = function() {
- setAssetListClassBit(2, true);
- var onDone = function(details) {
- if ( details.changedCount !== 0 ) {
- messaging.tell({ what: 'loadUpdatableAssets' });
- }
- };
- messaging.ask({ what: 'launchAssetUpdater', list: updateList }, onDone);
-};
-
-/******************************************************************************/
-
-var updateAssetsList = function() {
- messaging.ask({ what: 'getAssetUpdaterList' }, renderAssetList);
-};
-
-/******************************************************************************/
-
-// Updating all assets could be done from elsewhere and if so the
-// list here needs to be updated.
-
-var onAnnounce = function(msg) {
- switch ( msg.what ) {
- case 'allLocalAssetsUpdated':
- updateAssetsList();
- break;
-
- default:
- break;
- }
-};
-
-messaging.start('about.js');
-messaging.listen(onAnnounce);
-
-/******************************************************************************/
-
uDom('#aboutVersion').html(chrome.runtime.getManifest().version);
-uDom('#aboutAssetsUpdateButton').on('click', updateAssets);
-
-/******************************************************************************/
-
-updateAssetsList();
/******************************************************************************/
diff --git a/js/asset-updater.js b/js/asset-updater.js
deleted file mode 100644
index 546682bb6..000000000
--- a/js/asset-updater.js
+++ /dev/null
@@ -1,227 +0,0 @@
-/*******************************************************************************
-
- µBlock - a Chromium browser extension to block requests.
- Copyright (C) 2014 Raymond Hill
-
- 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
-*/
-
-/* global chrome, µBlock */
-
-/******************************************************************************/
-
-// Asset update manager
-
-µBlock.assetUpdater = (function() {
-
-/******************************************************************************/
-
-var getUpdateList = function(callback) {
- var localChecksumsText = '';
- var remoteChecksumsText = '';
-
- var compareChecksums = function() {
- var parseChecksumsText = function(text) {
- var result = {};
- var lines = text.split(/\n+/);
- var i = lines.length;
- var fields;
- while ( i-- ) {
- fields = lines[i].trim().split(/\s+/);
- if ( fields.length !== 2 ) {
- continue;
- }
- result[fields[1]] = fields[0];
- }
- return result;
- };
- if ( remoteChecksumsText === 'Error' || localChecksumsText === 'Error' ) {
- remoteChecksumsText = localChecksumsText = '';
- }
- var localAssetChecksums = parseChecksumsText(localChecksumsText);
- var remoteAssetChecksums = parseChecksumsText(remoteChecksumsText);
-
- var toUpdate = {};
- var path;
- for ( path in remoteAssetChecksums ) {
- if ( !remoteAssetChecksums.hasOwnProperty(path) ) {
- continue;
- }
- if ( localAssetChecksums[path] === undefined ) {
- toUpdate[path] = {
- status: 'Added',
- remoteChecksum: remoteAssetChecksums[path],
- localChecksum: ''
- };
- continue;
- }
- if ( localAssetChecksums[path] === remoteAssetChecksums[path] ) {
- toUpdate[path] = {
- status: 'Unchanged',
- remoteChecksum: remoteAssetChecksums[path],
- localChecksum: localAssetChecksums[path]
- };
- continue;
- }
- toUpdate[path] = {
- status: 'Changed',
- remoteChecksum: remoteAssetChecksums[path],
- localChecksum: localAssetChecksums[path]
- };
- }
- for ( path in localAssetChecksums ) {
- if ( !localAssetChecksums.hasOwnProperty(path) ) {
- continue;
- }
- if ( remoteAssetChecksums[path] === undefined ) {
- toUpdate[path] = {
- status: 'Removed',
- remoteChecksum: '',
- localChecksum: localAssetChecksums[path]
- };
- }
- }
-
- callback({ 'list': toUpdate });
- };
-
- var validateChecksums = function(details) {
- if ( details.error || details.content === '' ) {
- return 'Error';
- }
- if ( /^(?:[0-9a-f]{32}\s+\S+(\s+|$))+/.test(details.content) ) {
- return details.content;
- }
- return 'Error';
- };
-
- var onLocalChecksumsLoaded = function(details) {
- localChecksumsText = validateChecksums(details);
- if ( remoteChecksumsText !== '' ) {
- compareChecksums();
- }
- };
-
- var onRemoteChecksumsLoaded = function(details) {
- remoteChecksumsText = validateChecksums(details);
- if ( localChecksumsText !== '' ) {
- compareChecksums();
- }
- };
-
- µBlock.assets.getRepo('assets/checksums.txt', onRemoteChecksumsLoaded);
- µBlock.assets.get('assets/checksums.txt', onLocalChecksumsLoaded);
-};
-
-/******************************************************************************/
-
-// If `list` is null, it will be fetched internally.
-
-var update = function(list, callback) {
- var assetChangedCount = 0;
- var assetProcessedCount;
- var updatedAssetChecksums = [];
-
- var onCompleted = function() {
- var details = {
- what: 'allLocalAssetsUpdated',
- changedCount: assetChangedCount
- };
- callback(details);
- µBlock.messaging.announce(details);
- };
-
- var doCountdown = function() {
- assetProcessedCount -= 1;
- if ( assetProcessedCount > 0 ) {
- return;
- }
- µBlock.assets.put(
- 'assets/checksums.txt',
- updatedAssetChecksums.join('\n'),
- onCompleted
- );
- chrome.storage.local.set({ 'assetsUpdateTimestamp': Date.now() });
- };
-
- var assetUpdated = function(details) {
- var path = details.path;
- var entry = list[path];
- if ( details.error ) {
- updatedAssetChecksums.push(entry.localChecksum + ' ' + path);
- } else {
- updatedAssetChecksums.push(entry.remoteChecksum + ' ' + path);
- assetChangedCount += 1;
- }
- doCountdown();
- };
-
- var processList = function() {
- assetProcessedCount = Object.keys(list).length;
- if ( assetProcessedCount === 0 ) {
- onCompleted();
- return;
- }
- var entry;
- var details = { path: '', md5: '' };
- for ( var path in list ) {
- if ( list.hasOwnProperty(path) === false ) {
- continue;
- }
- entry = list[path];
- if ( entry.status === 'Added' || entry.status === 'Changed' ) {
- details.path = path;
- details.md5 = entry.remoteChecksum;
- µBlock.assets.update(details, assetUpdated);
- continue;
- }
- if ( entry.status === 'Unchanged' ) {
- updatedAssetChecksums.push(entry.localChecksum + ' ' + path);
- }
- doCountdown();
- }
- };
-
- var listLoaded = function(details) {
- list = details.list;
- processList();
- };
-
- // Purge obsolete external assets
- µBlock.assets.purge(/^https?:\/\/.+$/, Date.now() - µBlock.updateAssetsEvery);
-
- if ( list ) {
- processList();
- } else {
- getUpdateList(listLoaded);
- }
-};
-
-/******************************************************************************/
-
-// Export API
-
-return {
- 'getList': getUpdateList,
- 'update': update
-};
-
-/******************************************************************************/
-
-})();
-
-/******************************************************************************/
-
diff --git a/js/assets.js b/js/assets.js
index 3f202d13c..e57247c4e 100644
--- a/js/assets.js
+++ b/js/assets.js
@@ -23,20 +23,6 @@
/*******************************************************************************
-Assets
- Read:
- If in cache
- Use cache
- If not in cache
- Use local
- Update:
- Use remote
- Save in cache
-
- Import:
- Use textarea
- Save in cache [user directory]
-
File system structure:
assets
ublock
@@ -44,14 +30,11 @@ File system structure:
thirdparties
...
user
- blacklisted-hosts.txt
- ...
+ filters.txt
+ ...
*/
-// Ref: http://www.w3.org/TR/2012/WD-file-system-api-20120417/
-// Ref: http://www.html5rocks.com/en/tutorials/file/filesystem/
-
/******************************************************************************/
// Low-level asset files manager
@@ -60,11 +43,31 @@ File system structure:
/******************************************************************************/
-var fileSystem;
-var fileSystemQuota = 40 * 1024 * 1024;
var repositoryRoot = µBlock.projectServerRoot;
var nullFunc = function() {};
-var thirdpartyHomeURLs = null;
+var reIsExternalPath = /^https?:\/\/[a-z0-9]/;
+var reIsUserPath = /^assets\/user\//;
+
+var exports = {
+ autoUpdate: true,
+ autoUpdateDelay: 2 * 24 * 60 * 60 * 1000
+};
+
+/******************************************************************************/
+
+var AssetEntry = function() {
+ this.localChecksum = '';
+ this.repoChecksum = '';
+ this.expireTimestamp = 0;
+ this.homeURL = '';
+};
+
+var RepoMetadata = function() {
+ this.entries = {};
+ this.waiting = [];
+};
+
+var repoMeta = null;
/******************************************************************************/
@@ -78,6 +81,18 @@ var cachedAssetsManager = (function() {
callback(entries);
return;
}
+ // Flush cached non-user assets if these are from a prior version.
+ // https://github.com/gorhill/httpswitchboard/issues/212
+ var onLastVersionRead = function(store) {
+ var currentVersion = chrome.runtime.getManifest().version;
+ var lastVersion = store.extensionLastVersion || '0.0.0.0';
+ if ( currentVersion !== lastVersion ) {
+ chrome.storage.local.set({ 'extensionLastVersion': currentVersion });
+ exports.remove(/^assets\/(ublock|thirdparties)\//);
+ exports.remove('assets/checksums.txt');
+ }
+ callback(entries);
+ };
var onLoaded = function(bin) {
// https://github.com/gorhill/httpswitchboard/issues/381
// Maybe the index was requested multiple times and already
@@ -91,7 +106,7 @@ var cachedAssetsManager = (function() {
}
entries = bin.cached_asset_entries || {};
}
- callback(entries);
+ chrome.storage.local.get('extensionLastVersion', onLastVersionRead);
};
chrome.storage.local.get('cached_asset_entries', onLoaded);
};
@@ -146,10 +161,8 @@ var cachedAssetsManager = (function() {
}
};
var onEntries = function(entries) {
- if ( entries[path] === undefined ) {
- entries[path] = Date.now();
- bin.cached_asset_entries = entries;
- }
+ entries[path] = Date.now();
+ bin.cached_asset_entries = entries;
chrome.storage.local.set(bin, onSaved);
};
getEntries(onEntries);
@@ -202,173 +215,177 @@ var getTextFileFromURL = function(url, onLoad, onError) {
/******************************************************************************/
-// https://github.com/gorhill/uBlock/issues/89
-// Remove when I am confident everybody moved to the new storage
-
-// Useful to avoid having to manage a directory tree
-
-var cachePathFromPath = function(path) {
- return path.replace(/\//g, '___');
-};
-
-/******************************************************************************/
-
-// https://github.com/gorhill/uBlock/issues/89
-// Remove when I am confident everybody moved to the new storage
-
-var requestFileSystem = function(onSuccess, onError) {
- if ( fileSystem ) {
- onSuccess(fileSystem);
- return;
+var updateLocalChecksums = function() {
+ var localChecksums = [];
+ var entries = repoMeta.entries;
+ var entry;
+ for ( var path in entries ) {
+ if ( entries.hasOwnProperty(path) === false ) {
+ continue;
+ }
+ entry = entries[path];
+ if ( entry.localChecksum !== '' ) {
+ localChecksums.push(entry.localChecksum + ' ' + path);
+ }
}
-
- var onRequestFileSystem = function(fs) {
- fileSystem = fs;
- onSuccess(fs);
- };
-
- var onRequestQuota = function(grantedBytes) {
- window.webkitRequestFileSystem(window.PERSISTENT, grantedBytes, onRequestFileSystem, onError);
- };
-
- navigator.webkitPersistentStorage.requestQuota(fileSystemQuota, onRequestQuota, onError);
+ cachedAssetsManager.save('assets/checksums.txt', localChecksums.join('\n'));
};
/******************************************************************************/
-// https://github.com/gorhill/uBlock/issues/89
-// Remove when I am confident everybody moved to the new storage
+// Gather meta data of all assets.
-var oldReadCachedFile = function(path, callback) {
- var reportBack = function(content, err) {
- var details = {
- 'path': path,
- 'content': content,
- 'error': err
- };
- callback(details);
- };
+var getRepoMeta = function(callback, update) {
+ callback = callback || nullFunc;
- var onCacheFileLoaded = function() {
- // console.log('µBlock> readLocalFile() / onCacheFileLoaded()');
- reportBack(this.responseText);
- this.onload = this.onerror = null;
- };
-
- var onCacheFileError = function(ev) {
- // This handler may be called under normal circumstances: it appears
- // the entry may still be present even after the file was removed.
- // console.error('µBlock> readLocalFile() / onCacheFileError("%s")', path);
- reportBack('', 'Error');
- this.onload = this.onerror = null;
- };
-
- var onCacheEntryFound = function(entry) {
- // console.log('µBlock> readLocalFile() / onCacheEntryFound():', entry.toURL());
- // rhill 2014-04-18: `ublock` query parameter is added to ensure
- // the browser cache is bypassed.
- getTextFileFromURL(entry.toURL() + '?ublock=' + Date.now(), onCacheFileLoaded, onCacheFileError);
- };
-
- var onCacheEntryError = function(err) {
- if ( err.name !== 'NotFoundError' ) {
- console.error('µBlock> readLocalFile() / onCacheEntryError("%s"):', path, err.name);
- }
- reportBack('', 'Error');
- };
-
- var onRequestFileSystemSuccess = function(fs) {
- fs.root.getFile(cachePathFromPath(path), null, onCacheEntryFound, onCacheEntryError);
- };
-
- var onRequestFileSystemError = function(err) {
- console.error('µBlock> readLocalFile() / onRequestFileSystemError():', err.name);
- reportBack('', 'Error');
- };
-
- requestFileSystem(onRequestFileSystemSuccess, onRequestFileSystemError);
-};
-
-/******************************************************************************/
-
-// Flush cached non-user assets if these are from a prior version.
-// https://github.com/gorhill/httpswitchboard/issues/212
-
-var cacheSynchronized = false;
-
-var synchronizeCache = function() {
- if ( cacheSynchronized ) {
- return;
+ if ( update ) {
+ repoMeta = null;
}
- cacheSynchronized = true;
-
- // https://github.com/gorhill/uBlock/issues/89
- // Remove when I am confident everybody moved to the new storage
-
- var directoryReader;
- var done = function() {
- directoryReader = null;
- };
-
- var onReadEntries = function(entries) {
- var n = entries.length;
- if ( !n ) {
- return done();
- }
- var entry;
- for ( var i = 0; i < n; i++ ) {
- entry = entries[i];
- entry.remove(nullFunc);
- }
- // Read entries until none returned.
- directoryReader.readEntries(onReadEntries, onReadEntriesError);
- };
-
- var onReadEntriesError = function(err) {
- console.error('µBlock> synchronizeCache() / onReadEntriesError("%s"):', err.name);
- done();
- };
-
- var onRequestFileSystemSuccess = function(fs) {
- directoryReader = fs.root.createReader();
- directoryReader.readEntries(onReadEntries, onReadEntriesError);
- };
-
- var onRequestFileSystemError = function(err) {
- console.error('µBlock> synchronizeCache() / onRequestFileSystemError():', err.name);
- done();
- };
-
- var onLastVersionRead = function(store) {
- var currentVersion = chrome.runtime.getManifest().version;
- var lastVersion = store.extensionLastVersion || '0.0.0.0';
- if ( currentVersion === lastVersion ) {
- return done();
- }
- chrome.storage.local.set({ 'extensionLastVersion': currentVersion });
- cachedAssetsManager.remove(/^assets\/(ublock|thirdparties)\//);
- cachedAssetsManager.remove('assets/checksums.txt');
- requestFileSystem(onRequestFileSystemSuccess, onRequestFileSystemError);
- };
-
- // https://github.com/gorhill/uBlock/issues/89
- // Backward compatiblity.
-
- var onUserFiltersSaved = function() {
- chrome.storage.local.get('extensionLastVersion', onLastVersionRead);
- };
-
- var onUserFiltersLoaded = function(details) {
- if ( details.content !== '' ) {
- cachedAssetsManager.save(details.path, details.content, onUserFiltersSaved);
+ if ( repoMeta !== null ) {
+ if ( repoMeta.waiting.length !== 0 ) {
+ repoMeta.waiting.push(callback);
} else {
- chrome.storage.local.get('extensionLastVersion', onLastVersionRead);
+ callback(repoMeta);
+ }
+ return;
+ }
+
+ // https://github.com/gorhill/uBlock/issues/84
+ // First try to load from the actual home server of a third-party.
+ var parseHomeURLs = function(text) {
+ var entries = repoMeta.entries;
+ var urlPairs = text.split(/\n\n+/);
+ var i = urlPairs.length;
+ var pair, pos, k, v;
+ while ( i-- ) {
+ pair = urlPairs[i];
+ pos = pair.indexOf('\n');
+ if ( pos === -1 ) {
+ continue;
+ }
+ k = 'assets/thirdparties/' + pair.slice(0, pos).trim();
+ v = pair.slice(pos).trim();
+ if ( k === '' || v === '' ) {
+ continue;
+ }
+ if ( entries[k] === undefined ) {
+ entries[k] = new AssetEntry();
+ }
+ entries[k].homeURL = v;
+ }
+ while ( callback = repoMeta.waiting.pop() ) {
+ callback(repoMeta);
}
};
- oldReadCachedFile('assets/user/filters.txt', onUserFiltersLoaded);
+ var pathToHomeURLs = 'assets/ublock/thirdparty-lists.txt';
+
+ var onLocalHomeURLsLoaded = function(details) {
+ parseHomeURLs(details.content);
+ };
+
+ var onRepoHomeURLsLoaded = function(details) {
+ var entries = repoMeta.entries;
+ var entry = entries[pathToHomeURLs];
+ if ( YaMD5.hashStr(details.content) !== entry.repoChecksum ) {
+ entry.repoChecksum = entry.localChecksum;
+ readLocalFile(pathToHomeURLs, onLocalHomeURLsLoaded);
+ return;
+ }
+ cachedAssetsManager.save(pathToHomeURLs, details.content, onLocalHomeURLsLoaded);
+ };
+
+ var checksumCountdown = 2;
+ var localChecksums = '';
+ var repoChecksums = '';
+
+ var checksumsReceived = function() {
+ checksumCountdown -= 1;
+ if ( checksumCountdown !== 0 ) {
+ return;
+ }
+ // Remove from cache assets which no longer exist
+ var entries = repoMeta.entries;
+ var checksumsChanged = false;
+ var entry;
+ for ( var path in entries ) {
+ if ( entries.hasOwnProperty(path) === false ) {
+ continue;
+ }
+ entry = entries[path];
+ // If repo checksums could not be fetched, assume no change
+ if ( repoChecksums === '' ) {
+ entry.repoChecksum = entry.localChecksum;
+ }
+ if ( entry.repoChecksum !== '' || entry.localChecksum === '' ) {
+ continue;
+ }
+ checksumsChanged = true;
+ cachedAssetsManager.remove(path);
+ entry.localChecksum = '';
+ }
+ if ( checksumsChanged ) {
+ updateLocalChecksums();
+ }
+ // Fetch and store homeURL associations
+ entry = entries[pathToHomeURLs];
+ if ( entry.localChecksum !== entry.repoChecksum ) {
+ readRepoFile(pathToHomeURLs, onRepoHomeURLsLoaded);
+ } else {
+ readLocalFile(pathToHomeURLs, onLocalHomeURLsLoaded);
+ }
+ };
+
+ var validateChecksums = function(details) {
+ if ( details.error || details.content === '' ) {
+ return '';
+ }
+ if ( /^(?:[0-9a-f]{32}\s+\S+(?:\s+|$))+/.test(details.content) === false ) {
+ return '';
+ }
+ return details.content;
+ };
+
+ var parseChecksums = function(text, which) {
+ var entries = repoMeta.entries;
+ var lines = text.split(/\n+/);
+ var i = lines.length;
+ var fields, assetPath;
+ while ( i-- ) {
+ fields = lines[i].trim().split(/\s+/);
+ if ( fields.length !== 2 ) {
+ continue;
+ }
+ assetPath = fields[1];
+ if ( entries[assetPath] === undefined ) {
+ entries[assetPath] = new AssetEntry();
+ }
+ entries[assetPath][which + 'Checksum'] = fields[0];
+ }
+ };
+
+ var onLocalChecksumsLoaded = function(details) {
+ if ( localChecksums = validateChecksums(details) ) {
+ parseChecksums(localChecksums, 'local');
+ }
+ checksumsReceived();
+ };
+
+ var onRepoChecksumsLoaded = function(details) {
+ if ( repoChecksums = validateChecksums(details) ) {
+ parseChecksums(repoChecksums, 'repo');
+ }
+ checksumsReceived();
+ };
+
+ repoMeta = new RepoMetadata();
+ repoMeta.waiting.push(callback);
+ readRepoFile('assets/checksums.txt', onRepoChecksumsLoaded);
+ readLocalFile('assets/checksums.txt', onLocalChecksumsLoaded);
};
+// https://www.youtube.com/watch?v=-t3WYfgM4x8
+
/******************************************************************************/
var readLocalFile = function(path, callback) {
@@ -389,7 +406,7 @@ var readLocalFile = function(path, callback) {
this.onload = this.onerror = null;
};
- var onLocalFileError = function(ev) {
+ var onLocalFileError = function() {
console.error('µBlock> readLocalFile() / onLocalFileError("%s")', path);
reportBack('', 'Error');
this.onload = this.onerror = null;
@@ -423,6 +440,7 @@ var readRepoFile = function(path, callback) {
};
var onRepoFileLoaded = function() {
+ this.onload = this.onerror = null;
// console.log('µBlock> readRepoFile() / onRepoFileLoaded()');
// https://github.com/gorhill/httpswitchboard/issues/263
if ( this.status === 200 ) {
@@ -430,13 +448,12 @@ var readRepoFile = function(path, callback) {
} else {
reportBack('', 'Error ' + this.statusText);
}
- this.onload = this.onerror = null;
};
- var onRepoFileError = function(ev) {
+ var onRepoFileError = function() {
+ this.onload = this.onerror = null;
console.error('µBlock> readRepoFile() / onRepoFileError("%s")', path);
reportBack('', 'Error');
- this.onload = this.onerror = null;
};
// 'ublock=...' is to skip browser cache
@@ -473,19 +490,19 @@ var readExternalFile = function(path, callback) {
cachedAssetsManager.save(path, this.responseText, onExternalFileCached);
};
- var onExternalFileError = function(ev) {
+ var onExternalFileError = function() {
console.error('µBlock> onExternalFileError() / onLocalFileError("%s")', path);
reportBack('', 'Error');
this.onload = this.onerror = null;
};
var onCachedContentLoaded = function(details) {
- // console.log('µBlock> readLocalFile() / onCacheFileLoaded()');
+ // console.log('µBlock> readExternalFile() / onCacheFileLoaded()');
reportBack(details.content);
};
var onCachedContentError = function(details) {
- // console.error('µBlock> readLocalFile() / onCacheFileError("%s")', path);
+ // console.error('µBlock> readExternalFile() / onCacheFileError("%s")', path);
getTextFileFromURL(details.path, onExternalFileLoaded, onExternalFileError);
};
@@ -494,162 +511,518 @@ var readExternalFile = function(path, callback) {
/******************************************************************************/
-var purgeCache = function(pattern, before) {
- cachedAssetsManager.remove(pattern, before);
+// An asset from an external source with a copy shipped with the extension:
+// Path --> starts with 'assets/(thirdparties|ublock)/', with a home URL
+// External -->
+// Repository --> has checksum (to detect need for update only)
+// Cache --> has expiration timestamp (in cache)
+// Local --> install time version
+
+var readRepoCopyAsset = function(path, callback) {
+ var assetEntry;
+
+ var reportBack = function(content, err) {
+ var details = {
+ 'path': path,
+ 'content': content
+ };
+ if ( err ) {
+ details.error = err;
+ }
+ callback(details);
+ };
+
+ var updateChecksum = function() {
+ if ( assetEntry !== undefined ) {
+ if ( assetEntry.repoChecksum !== assetEntry.localChecksum ) {
+ assetEntry.localChecksum = assetEntry.repoChecksum;
+ updateLocalChecksums();
+ }
+ }
+ };
+
+ var onInstallFileLoaded = function() {
+ this.onload = this.onerror = null;
+ console.log('µBlock> readRepoCopyAsset("%s") / onInstallFileLoaded()', path);
+ reportBack(this.responseText);
+ };
+
+ var onInstallFileError = function() {
+ this.onload = this.onerror = null;
+ console.error('µBlock> readRepoCopyAsset("%s") / onInstallFileError():', path, this.statusText);
+ reportBack('', 'Error');
+ };
+
+ var onCachedContentLoaded = function(details) {
+ console.log('µBlock> readRepoCopyAsset("%s") / onCacheFileLoaded()', path);
+ reportBack(details.content);
+ };
+
+ var onCachedContentError = function(details) {
+ console.log('µBlock> readRepoCopyAsset("%s") / onCacheFileError()', path);
+ getTextFileFromURL(chrome.runtime.getURL(details.path), onInstallFileLoaded, onInstallFileError);
+ };
+
+ var repositoryURL = repositoryRoot + path + '?ublock=' + Date.now();
+
+ var onRepoFileLoaded = function() {
+ this.onload = this.onerror = null;
+ if ( typeof this.responseText !== 'string' || this.responseText === '' ) {
+ console.error('µBlock> readRepoCopyAsset("%s") / onRepoFileLoaded("%s"): no response', path, repositoryURL);
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+ return;
+ }
+ console.debug('µBlock> readRepoCopyAsset("%s") / onRepoFileLoaded("%s")', path, repositoryURL);
+ updateChecksum();
+ cachedAssetsManager.save(path, this.responseText, callback);
+ };
+
+ var onRepoFileError = function() {
+ this.onload = this.onerror = null;
+ console.error('µBlock> readRepoCopyAsset("%s") / onRepoFileError("%s"):', path, repositoryURL, this.statusText);
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+ };
+
+ var onHomeFileLoaded = function() {
+ this.onload = this.onerror = null;
+ if ( typeof this.responseText !== 'string' || this.responseText === '' ) {
+ console.error('µBlock> readRepoCopyAsset("%s") / onHomeFileLoaded("%s"): no response', path, assetEntry.homeURL);
+ // Fetch from repo only if obsolescence was due to repo checksum
+ if ( assetEntry.localChecksum !== assetEntry.repoChecksum ) {
+ getTextFileFromURL(repositoryURL, onRepoFileLoaded, onRepoFileError);
+ } else {
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+ }
+ return;
+ }
+ console.debug('µBlock> readRepoCopyAsset("%s") / onHomeFileLoaded("%s")', path, assetEntry.homeURL);
+ updateChecksum();
+ cachedAssetsManager.save(path, this.responseText, callback);
+ };
+
+ var onHomeFileError = function() {
+ this.onload = this.onerror = null;
+ console.error('µBlock> readRepoCopyAsset("%s") / onHomeFileError("%s"):', path, assetEntry.homeURL, this.statusText);
+ // Fetch from repo only if obsolescence was due to repo checksum
+ if ( assetEntry.localChecksum !== assetEntry.repoChecksum ) {
+ getTextFileFromURL(repositoryURL, onRepoFileLoaded, onRepoFileError);
+ } else {
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+ }
+ };
+
+ var onCacheMetaReady = function(entries) {
+ var timestamp = entries[path];
+ // In cache
+ if ( typeof timestamp === 'number' ) {
+ // Verify obsolescence
+ if ( exports.autoUpdate ) {
+ var obsolete = Date.now() - exports.autoUpdateDelay;
+ if ( timestamp <= obsolete ) {
+ console.log('µBlock> readRepoCopyAsset("%s") / onCacheMetaReady(): cache is obsolete', path);
+ getTextFileFromURL(assetEntry.homeURL, onHomeFileLoaded, onHomeFileError);
+ return;
+ }
+ }
+ // Not obsolete
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+ return;
+ }
+ // Not in cache
+ getTextFileFromURL(chrome.runtime.getURL(path), onInstallFileLoaded, onInstallFileError);
+ };
+
+ var onRepoMetaReady = function(meta) {
+ assetEntry = meta.entries[path];
+
+ // Asset doesn't exist
+ if ( assetEntry === undefined ) {
+ reportBack('', 'Error: Asset not found');
+ return;
+ }
+
+ // Repo copy changed: fetch from home URL
+ if ( exports.autoUpdate ) {
+ if ( assetEntry.localChecksum !== assetEntry.repoChecksum ) {
+ console.log('µBlock> readRepoCopyAsset("%s") / onRepoMetaReady(): repo has newer version', path);
+ getTextFileFromURL(assetEntry.homeURL, onHomeFileLoaded, onHomeFileError);
+ return;
+ }
+ }
+
+ // No change, we will inspect the cached version for obsolescence
+ cachedAssetsManager.entries(onCacheMetaReady);
+ };
+
+ getRepoMeta(onRepoMetaReady);
+};
+
+// https://www.youtube.com/watch?v=uvUW4ozs7pY
+
+/******************************************************************************/
+
+// An important asset shipped with the extension -- typically small, or
+// doesn't change often:
+// Path --> starts with 'assets/(thirdparties|ublock)/', without a home URL
+// Repository --> has checksum (to detect need for update and corruption)
+// Cache --> whatever from above
+// Local --> install time version
+
+var readRepoOnlyAsset = function(path, callback) {
+
+ var assetEntry;
+
+ var reportBack = function(content, err) {
+ var details = {
+ 'path': path,
+ 'content': content
+ };
+ if ( err ) {
+ details.error = err;
+ }
+ callback(details);
+ };
+
+ var onInstallFileLoaded = function() {
+ this.onload = this.onerror = null;
+ console.log('µBlock> readRepoOnlyAsset("%s") / onInstallFileLoaded()', path);
+ reportBack(this.responseText);
+ };
+
+ var onInstallFileError = function() {
+ this.onload = this.onerror = null;
+ console.error('µBlock> readRepoOnlyAsset("%s") / onInstallFileError()', path);
+ reportBack('', 'Error');
+ };
+
+ var onCachedContentLoaded = function(details) {
+ console.log('µBlock> readRepoOnlyAsset("%s") / onCachedContentLoaded()', path);
+ reportBack(details.content);
+ };
+
+ var onCachedContentError = function() {
+ console.log('µBlock> readRepoOnlyAsset("%s") / onCachedContentError()', path);
+ getTextFileFromURL(chrome.runtime.getURL(path), onInstallFileLoaded, onInstallFileError);
+ };
+
+ var repositoryURL = repositoryRoot + path + '?ublock=' + Date.now();
+
+ var onRepoFileLoaded = function() {
+ this.onload = this.onerror = null;
+ if ( typeof this.responseText !== 'string' ) {
+ console.error('µBlock> readRepoOnlyAsset("%s") / onRepoFileLoaded("%s"): no response', path, repositoryURL);
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+ return;
+ }
+ if ( YaMD5.hashStr(this.responseText) !== assetEntry.repoChecksum ) {
+ console.error('µBlock> readRepoOnlyAsset("%s") / onRepoFileLoaded("%s"): bad md5 checksum', path, repositoryURL);
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+ return;
+ }
+ console.debug('µBlock> readRepoOnlyAsset("%s") / onRepoFileLoaded("%s")', path, repositoryURL);
+ assetEntry.localChecksum = assetEntry.repoChecksum;
+ updateLocalChecksums();
+ cachedAssetsManager.save(path, this.responseText, callback);
+ };
+
+ var onRepoFileError = function() {
+ this.onload = this.onerror = null;
+ console.error('µBlock> readRepoOnlyAsset("%s") / onRepoFileError("%s"):', path, repositoryURL, this.statusText);
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+ };
+
+ var onRepoMetaReady = function(meta) {
+ assetEntry = meta.entries[path];
+
+ // Asset doesn't exist
+ if ( assetEntry === undefined ) {
+ reportBack('', 'Error: Asset not found');
+ return;
+ }
+
+ // Asset added or changed: load from repo URL and then cache result
+ if ( exports.autoUpdate ) {
+ if ( assetEntry.localChecksum !== assetEntry.repoChecksum ) {
+ console.log('µBlock> readRepoOnlyAsset("%s") / onRepoMetaReady(): repo has newer version', path);
+ getTextFileFromURL(repositoryURL, onRepoFileLoaded, onRepoFileError);
+ return;
+ }
+ }
+
+ // Asset unchanged: load from cache
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+ };
+
+ getRepoMeta(onRepoMetaReady);
};
/******************************************************************************/
-var writeLocalFile = function(path, content, callback) {
+// Asset doesn't exist. Just for symmetry purpose.
+
+var readNilAsset = function(path, callback) {
+ callback({
+ 'path': path,
+ 'content': '',
+ 'error': 'Error: asset not found'
+ });
+};
+
+/******************************************************************************/
+
+// An external asset:
+// Path --> starts with 'http'
+// External --> https://..., http://...
+// Cache --> has expiration timestamp (in cache)
+
+var readExternalAsset = function(path, callback) {
+ var reportBack = function(content, err) {
+ var details = {
+ 'path': path,
+ 'content': content
+ };
+ if ( err ) {
+ details.error = err;
+ }
+ callback(details);
+ };
+
+ var onCachedContentLoaded = function(details) {
+ console.log('µBlock> readExternalAsset("%s") / onCachedContentLoaded()', path);
+ reportBack(details.content);
+ };
+
+ var onCachedContentError = function() {
+ console.error('µBlock> readExternalAsset("%s") / onCachedContentError()', path);
+ reportBack('', 'Error');
+ };
+
+ var onExternalFileLoaded = function() {
+ this.onload = this.onerror = null;
+ console.log('µBlock> readExternalAsset("%s") / onExternalFileLoaded1()', path);
+ cachedAssetsManager.save(path, this.responseText);
+ reportBack(this.responseText);
+ };
+
+ var onExternalFileError2 = function() {
+ this.onload = this.onerror = null;
+ console.error('µBlock> readExternalAsset("%s") / onExternalFileError2()', path);
+ reportBack('', 'Error');
+ };
+
+ var onExternalFileError1 = function() {
+ this.onload = this.onerror = null;
+ console.error('µBlock> readExternalAsset("%s") / onExternalFileError1()', path);
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+ };
+
+ var entriesReady = function(entries) {
+ var timestamp = entries[path];
+ // In cache
+ if ( typeof timestamp === 'number' ) {
+ // Obsolete?
+ if ( exports.autoUpdate ) {
+ var obsolete = Date.now() - exports.autoUpdateDelay;
+ if ( timestamp <= obsolete ) {
+ getTextFileFromURL(path, onExternalFileLoaded, onExternalFileError1);
+ return;
+ }
+ }
+ // Not obsolete
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+ return;
+ }
+ // Not in cache
+ getTextFileFromURL(path, onExternalFileLoaded, onExternalFileError2);
+ };
+
+ cachedAssetsManager.entries(entriesReady);
+};
+
+/******************************************************************************/
+
+// User data:
+// Path --> starts with 'assets/user/'
+// Cache --> whatever user saved
+
+var readUserAsset = function(path, callback) {
+ var onCachedContentLoaded = function(details) {
+ console.log('µBlock> readUserAsset("%s") / onCachedContentLoaded()', path);
+ callback({ 'path': path, 'content': details.content });
+ };
+
+ var onCachedContentError = function() {
+ console.log('µBlock> readUserAsset("%s") / onCachedContentError()', path);
+ callback({ 'path': path, 'content': '' });
+ };
+
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+};
+
+/******************************************************************************/
+
+// Assets
+//
+// A copy of an asset from an external source shipped with the extension:
+// Path --> starts with 'assets/(thirdparties|ublock)/', with a home URL
+// External -->
+// Repository --> has checksum (to detect obsolescence)
+// Cache --> has expiration timestamp (to detect obsolescence)
+// Local --> install time version
+//
+// An important asset shipped with the extension (usually small, or doesn't
+// change often):
+// Path --> starts with 'assets/(thirdparties|ublock)/', without a home URL
+// Repository --> has checksum (to detect obsolescence or data corruption)
+// Cache --> whatever from above
+// Local --> install time version
+//
+// An external filter list:
+// Path --> starts with 'http'
+// External -->
+// Cache --> has expiration timestamp (to detect obsolescence)
+//
+// User data:
+// Path --> starts with 'assets/user/'
+// Cache --> whatever user saved
+//
+// When a checksum is present, it is used to determine whether the asset
+// needs to be updated.
+// When an expiration timestamp is present, it is used to determine whether
+// the asset needs to be updated.
+//
+// If no update required, an asset if first fetched from the cache. If the
+// asset is not cached it is fetched from the closest location: local for
+// an asset shipped with the extension, external for an asset not shipped
+// with the extension.
+
+exports.get = function(path, callback) {
+
+ if ( reIsUserPath.test(path) ) {
+ readUserAsset(path, callback);
+ return;
+ }
+
+ if ( reIsExternalPath.test(path) ) {
+ readExternalAsset(path, callback);
+ return;
+ }
+
+ var onRepoMetaReady = function(meta) {
+ var assetEntry = meta.entries[path];
+
+ // Asset doesn't exist
+ if ( assetEntry === undefined ) {
+ readNilAsset(path, callback);
+ return;
+ }
+
+ // Asset is repo copy of external content
+ if ( assetEntry.homeURL !== '' ) {
+ readRepoCopyAsset(path, callback);
+ return;
+ }
+
+ // Asset is repo only
+ readRepoOnlyAsset(path, callback);
+ };
+
+ getRepoMeta(onRepoMetaReady);
+};
+
+// https://www.youtube.com/watch?v=98y0Q7nLGWk
+
+/******************************************************************************/
+
+exports.put = function(path, content, callback) {
cachedAssetsManager.save(path, content, callback);
};
/******************************************************************************/
-var updateFromRemote = function(details, callback) {
- // 'ublock=...' is to skip browser cache
- var targetPath = details.path;
- // https://github.com/gorhill/uBlock/issues/84
- var homeURL = '';
- var repositoryURL = repositoryRoot + targetPath + '?ublock=' + Date.now();
- var targetMd5 = details.md5 || '';
+exports.purge = function(pattern, before) {
+ cachedAssetsManager.remove(pattern, before);
+};
- var reportBackError = function() {
- callback({
- 'path': targetPath,
- 'error': 'Error'
- });
- };
+/******************************************************************************/
- var onRepoFileLoaded = function() {
- this.onload = this.onerror = null;
- if ( typeof this.responseText !== 'string' ) {
- console.error('µBlock> updateFromRemote("%s") / onRepoFileLoaded(): no response', repositoryURL);
- reportBackError();
- return;
- }
- if ( targetMd5 !== '' && YaMD5.hashStr(this.responseText) !== targetMd5 ) {
- console.error('µBlock> updateFromRemote("%s") / onRepoFileLoaded(): bad md5 checksum', repositoryURL);
- reportBackError();
- return;
- }
- // console.debug('µBlock> updateFromRemote("%s") / onRepoFileLoaded()', repositoryURL);
- writeLocalFile(targetPath, this.responseText, callback);
- };
+exports.metadata = function(callback) {
+ var out = {};
- var onRepoFileError = function(ev) {
- this.onload = this.onerror = null;
- console.error('µBlock> updateFromRemote() / onRepoFileError("%s"):', repositoryURL, this.statusText);
- reportBackError();
- };
-
- var onHomeFileLoaded = function() {
- this.onload = this.onerror = null;
- if ( typeof this.responseText !== 'string' ) {
- console.error('µBlock> updateFromRemote("%s") / onHomeFileLoaded(): no response', homeURL);
- getTextFileFromURL(repositoryURL, onRepoFileLoaded, onRepoFileError);
- return;
- }
- if ( targetMd5 !== '' && YaMD5.hashStr(this.responseText) !== targetMd5 ) {
- console.error('µBlock> updateFromRemote("%s") / onHomeFileLoaded(): bad md5 checksum', homeURL);
- getTextFileFromURL(repositoryURL, onRepoFileLoaded, onRepoFileError);
- return;
- }
- // console.debug('µBlock> updateFromRemote("%s") / onHomeFileLoaded()', homeURL);
- writeLocalFile(targetPath, this.responseText, callback);
- };
-
- var onHomeFileError = function(ev) {
- this.onload = this.onerror = null;
- console.error('µBlock> updateFromRemote() / onHomeFileError("%s"):', homeURL, this.statusText);
- getTextFileFromURL(repositoryURL, onRepoFileLoaded, onRepoFileError);
- };
-
- // https://github.com/gorhill/uBlock/issues/84
- // Create a URL from where the asset needs to be downloaded. It can be:
- // - a home server URL
- // - a github repo URL
- var getThirdpartyHomeURL = function() {
- // If it is a 3rd-party, look-up home server URL, if any.
- if ( thirdpartyHomeURLs && targetPath.indexOf('assets/thirdparties/') === 0 ) {
- var k = targetPath.replace('assets/thirdparties/', '');
- if ( thirdpartyHomeURLs[k] ) {
- homeURL = thirdpartyHomeURLs[k];
- }
- }
- // If there is a home server, disregard checksum: the file is assumed
- // most up to date at the home server.
- if ( homeURL !== '' ) {
- targetMd5 = '';
- getTextFileFromURL(homeURL, onHomeFileLoaded, onHomeFileError);
- return;
- }
- // The resource will be pulled from Github repo. It's reserved for
- // more important assets, so we keep and use the checksum.
- getTextFileFromURL(repositoryURL, onRepoFileLoaded, onRepoFileError);
- };
-
- // https://github.com/gorhill/uBlock/issues/84
- // First try to load from the actual home server of a third-party.
- var onThirdpartyHomeURLsLoaded = function(details) {
- thirdpartyHomeURLs = {};
- if ( details.error ) {
- getThirdpartyHomeURL();
- return;
- }
- var urlPairs = details.content.split(/\n\n+/);
- var i = urlPairs.length;
- var pair, pos, k, v;
- while ( i-- ) {
- pair = urlPairs[i];
- pos = pair.indexOf('\n');
- if ( pos === -1 ) {
+ var onRepoMetaReady = function(meta) {
+ var entries = meta.entries;
+ var entryRepo, entryOut;
+ for ( var path in entries ) {
+ if ( entries.hasOwnProperty(path) === false ) {
continue;
}
- k = pair.slice(0, pos).trim();
- v = pair.slice(pos).trim();
- if ( k === '' || v === '' ) {
- continue;
+ entryRepo = entries[path];
+ entryOut = out[path];
+ if ( entryOut === undefined ) {
+ entryOut = out[path] = {};
}
- thirdpartyHomeURLs[k] = v;
+ entryOut.localChecksum = entryRepo.localChecksum;
+ entryOut.repoChecksum = entryRepo.repoChecksum;
+ entryOut.homeURL = entryRepo.homeURL;
+ entryOut.repoObsolete = entryOut.localChecksum !== entryOut.repoChecksum;
}
- getThirdpartyHomeURL();
+ callback(out);
};
- // Get home servers if not done yet.
- if ( thirdpartyHomeURLs === null ) {
- readLocalFile('assets/ublock/thirdparty-lists.txt', onThirdpartyHomeURLsLoaded);
- } else {
- getThirdpartyHomeURL();
- }
+ var onCacheMetaReady = function(entries) {
+ var obsolete = Date.now() - exports.autoUpdateDelay;
+ var entryOut;
+ for ( var path in entries ) {
+ if ( entries.hasOwnProperty(path) === false ) {
+ continue;
+ }
+ entryOut = out[path];
+ if ( entryOut === undefined ) {
+ entryOut = out[path] = {};
+ }
+ entryOut.lastModified = entries[path];
+ // User data is not literally cache data
+ if ( reIsUserPath.test(path) ) {
+ continue;
+ }
+ entryOut.cached = true;
+ entryOut.cacheObsolete = entryOut.lastModified <= obsolete;
+ if ( reIsExternalPath.test(path) ) {
+ entryOut.homeURL = path;
+ }
+ }
+ getRepoMeta(onRepoMetaReady);
+ };
+
+ cachedAssetsManager.entries(onCacheMetaReady);
};
/******************************************************************************/
-var cacheEntries = function(callback) {
- return cachedAssetsManager.entries(callback);
+exports.purgeAll = function(callback) {
+ var onMetaDataReady = function(entries) {
+ var out = {};
+ var entry;
+ for ( var path in entries ) {
+ if ( entries.hasOwnProperty(path) === false ) {
+ continue;
+ }
+ entry = entries[path];
+ if ( !entry.cacheObsolete && !entry.repoObsolete ) {
+ continue;
+ }
+ cachedAssetsManager.remove(path);
+ out[path] = true;
+ }
+ callback(out);
+ };
+
+ exports.metadata(onMetaDataReady);
};
/******************************************************************************/
-// Flush cached assets if cache content is from an older version: the extension
-// always ships with the most up-to-date assets.
-
-synchronizeCache();
-
-/******************************************************************************/
-
-// Export API
-
-return {
- 'get': readLocalFile,
- 'getExternal': readExternalFile,
- 'getRepo': readRepoFile,
- 'purge': purgeCache,
- 'put': writeLocalFile,
- 'update': updateFromRemote,
- 'entries': cacheEntries
-};
+return exports;
/******************************************************************************/
diff --git a/js/background.js b/js/background.js
index 7c966fc0a..ff72b985a 100644
--- a/js/background.js
+++ b/js/background.js
@@ -31,6 +31,7 @@ return {
manifest: chrome.runtime.getManifest(),
userSettings: {
+ autoUpdate: true,
collapseBlocked: true,
externalLists: '',
logBlockedRequests: false,
diff --git a/js/messaging-handlers.js b/js/messaging-handlers.js
index 3a93f81b5..460896f03 100644
--- a/js/messaging-handlers.js
+++ b/js/messaging-handlers.js
@@ -250,9 +250,10 @@ var getLists = function(callback) {
available: null,
current: µb.remoteBlacklists,
cosmetic: µb.userSettings.parseAllABPHideFilters,
+ autoUpdate: µb.userSettings.autoUpdate,
cache: null
};
- var onEntries = function(entries) {
+ var onMetadataReady = function(entries) {
r.cache = entries;
if ( r.available ) {
callback(r);
@@ -265,7 +266,7 @@ var getLists = function(callback) {
}
};
µb.getAvailableLists(onLists);
- µb.assets.entries(onEntries);
+ µb.assets.metadata(onMetadataReady);
};
/******************************************************************************/
@@ -475,20 +476,6 @@ var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
- case 'getAssetUpdaterList':
- return µb.assetUpdater.getList(callback);
-
- case 'launchAssetUpdater':
- return µb.assetUpdater.update(request.list, callback);
-
- case 'readUserSettings':
- return chrome.storage.local.get(µb.userSettings, callback);
-
- case 'readUserFilters':
- return µb.assets.get(µb.userFiltersPath, callback);
-
- case 'writeUserFilters':
- return µb.assets.put(µb.userFiltersPath, request.content, callback);
default:
break;
@@ -498,20 +485,6 @@ var onMessage = function(request, sender, callback) {
var response;
switch ( request.what ) {
- case 'loadUpdatableAssets':
- response = µb.loadUpdatableAssets();
- break;
-
- case 'readFilterListSelection':
- response = µb.remoteBlacklists;
- break;
-
- case 'getSomeStats':
- response = {
- storageQuota: µb.storageQuota,
- storageUsed: µb.storageUsed
- };
- break;
default:
return µb.messaging.defaultHandler(request, sender, callback);
@@ -524,4 +497,6 @@ var onMessage = function(request, sender, callback) {
})();
+// https://www.youtube.com/watch?v=3_WcygKJP1k
+
/******************************************************************************/
diff --git a/js/messaging.js b/js/messaging.js
index bc5d4e7ce..86c230937 100644
--- a/js/messaging.js
+++ b/js/messaging.js
@@ -132,15 +132,12 @@ var onMessage = function(request, port) {
/******************************************************************************/
-// Default is for commonly used message.
+// Default is for commonly used messages.
function defaultHandler(request, sender, callback) {
// Async
switch ( request.what ) {
case 'getAssetContent':
- if ( /^https?:\/\//.test(request.url) ) {
- return µBlock.assets.getExternal(request.url, callback);
- }
return µBlock.assets.get(request.url, callback);
case 'loadUbiquitousAllowRules':
@@ -171,7 +168,7 @@ function defaultHandler(request, sender, callback) {
break;
case 'reloadAllFilters':
- µBlock.reloadPresetBlacklists(request.switches);
+ µBlock.reloadPresetBlacklists(request.switches, request.update);
break;
case 'userSettings':
@@ -186,6 +183,8 @@ function defaultHandler(request, sender, callback) {
callback(response);
}
+// https://www.youtube.com/watch?v=rrzRgUAHqc8
+
/******************************************************************************/
// Port disconnected, relay this information to apropriate listener.
diff --git a/js/pagestore.js b/js/pagestore.js
index d541305c2..130215571 100644
--- a/js/pagestore.js
+++ b/js/pagestore.js
@@ -218,7 +218,7 @@ PageStore.prototype.updateBadgeFromTab = function(tab) {
var iconStr = '';
if ( µb.userSettings.showIconBadge && netFiltering && this.perLoadBlockedRequestCount ) {
- iconStr = this.perLoadBlockedRequestCount.toLocaleString();
+ iconStr = µb.utils.formatCount(this.perLoadBlockedRequestCount);
}
chrome.browserAction.setBadgeText({ tabId: tab.id, text: iconStr });
@@ -234,6 +234,8 @@ PageStore.prototype.updateBadge = function() {
chrome.tabs.get(this.tabId, this.updateBadgeFromTab.bind(this));
};
+// https://www.youtube.com/watch?v=drW8p_dTLD4
+
/******************************************************************************/
return {
diff --git a/js/start.js b/js/start.js
index 2ab08ba39..aef9f4a0b 100644
--- a/js/start.js
+++ b/js/start.js
@@ -35,18 +35,17 @@
(function() {
var µb = µBlock;
- var jobDone = function(details) {
- if ( details.changedCount === 0 ) {
+ var jobCallback = function() {
+ if ( µb.userSettings.autoUpdate !== true ) {
return;
}
- µb.loadUpdatableAssets();
- };
-
- var jobCallback = function() {
- µb.assetUpdater.update(null, jobDone);
+ // TODO: need smarter update, currently blindly reloading all.
+ µb.loadUpdatableAssets(true);
};
µb.asyncJobs.add('autoUpdateAssets', null, jobCallback, µb.updateAssetsEvery, true);
})();
+// https://www.youtube.com/watch?v=cIrGQD84F1g
+
/******************************************************************************/
diff --git a/js/storage.js b/js/storage.js
index 1073d2c9c..a8f1840d2 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -70,7 +70,7 @@
/******************************************************************************/
-µBlock.loadUserSettings = function() {
+µBlock.loadUserSettings = function(callback) {
var settingsLoaded = function(store) {
var µb = µBlock;
// Backward compatibility after fix to #5
@@ -88,6 +88,8 @@
µb.userSettings = store;
µb.netWhitelist = µb.whitelistFromString(store.netWhitelist);
µb.netWhitelistModifyTime = Date.now();
+
+ callback();
};
chrome.storage.local.get(this.userSettings, settingsLoaded);
@@ -265,11 +267,7 @@
blacklistLoadCount -= 1;
continue;
}
- if ( /^https?:\/\/.+$/.test(location) ) {
- µb.assets.getExternal(location, mergeBlacklist);
- } else {
- µb.assets.get(location, mergeBlacklist);
- }
+ µb.assets.get(location, mergeBlacklist);
}
};
@@ -400,7 +398,7 @@
// `switches` contains the preset blacklists for which the switch must be
// revisited.
-µBlock.reloadPresetBlacklists = function(switches) {
+µBlock.reloadPresetBlacklists = function(switches, update) {
var presetBlacklists = this.remoteBlacklists;
// Toggle switches, if any
@@ -420,7 +418,7 @@
}
// Now force reload
- this.loadUbiquitousBlacklists();
+ this.loadUpdatableAssets(update);
};
/******************************************************************************/
@@ -443,9 +441,11 @@
// Load updatable assets
-µBlock.loadUpdatableAssets = function() {
- this.loadUbiquitousBlacklists();
+µBlock.loadUpdatableAssets = function(update) {
+ this.assets.autoUpdate = update;
+ this.assets.autoUpdateDelay = this.updateAssetsEvery;
this.loadPublicSuffixList();
+ this.loadUbiquitousBlacklists();
};
/******************************************************************************/
@@ -453,11 +453,8 @@
// Load all
µBlock.load = function() {
- this.loadLocalSettings();
- this.loadUserSettings();
-
- // load updatable assets -- after updating them if needed
- this.assetUpdater.update(null, this.loadUpdatableAssets.bind(this));
-
+ // User settings need to be available for this because we need
+ // µBlock.userSettings.externalLists
+ this.loadUserSettings(this.loadUpdatableAssets.bind(this, this.userSettings.autoUpdate));
this.getBytesInUse();
};
diff --git a/js/ublock.js b/js/ublock.js
index 169efbb97..cce9aa51a 100644
--- a/js/ublock.js
+++ b/js/ublock.js
@@ -233,24 +233,3 @@
};
/******************************************************************************/
-
-µBlock.formatCount = function(count) {
- if ( typeof count !== 'number' ) {
- return '';
- }
- var s = count.toFixed(0);
- if ( count >= 1000 ) {
- if ( count < 10000 ) {
- s = '>' + s.slice(0,1) + 'K';
- } else if ( count < 100000 ) {
- s = s.slice(0,2) + 'K';
- } else if ( count < 1000000 ) {
- s = s.slice(0,3) + 'K';
- } else if ( count < 10000000 ) {
- s = s.slice(0,1) + 'M';
- } else {
- s = s.slice(0,-6) + 'M';
- }
- }
- return s;
-};
diff --git a/js/utils.js b/js/utils.js
index 0b534dee0..65cc9976c 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -29,7 +29,11 @@
/******************************************************************************/
-var gotoURL = function(details) {
+var exports = {};
+
+/******************************************************************************/
+
+exports.gotoURL = function(details) {
if ( details.tabId ) {
chrome.tabs.update(details.tabId, { url: details.url });
} else {
@@ -39,7 +43,7 @@ var gotoURL = function(details) {
/******************************************************************************/
-var gotoExtensionURL = function(url) {
+exports.gotoExtensionURL = function(url) {
var hasQuery = function(url) {
return url.indexOf('?') >= 0;
@@ -98,11 +102,33 @@ var gotoExtensionURL = function(url) {
/******************************************************************************/
-return {
- gotoURL: gotoURL,
- gotoExtensionURL: gotoExtensionURL
+exports.formatCount = function(count) {
+ if ( typeof count !== 'number' ) {
+ return '';
+ }
+ var s = count.toFixed(0);
+ if ( count >= 1000 ) {
+ if ( count < 10000 ) {
+ s = '>' + s.slice(0,1) + 'K';
+ } else if ( count < 100000 ) {
+ s = s.slice(0,2) + 'K';
+ } else if ( count < 1000000 ) {
+ s = s.slice(0,3) + 'K';
+ } else if ( count < 10000000 ) {
+ s = s.slice(0,1) + 'M';
+ } else {
+ s = s.slice(0,-6) + 'M';
+ }
+ }
+ return s;
};
+// https://www.youtube.com/watch?v=uvUW4ozs7pY
+
+/******************************************************************************/
+
+return exports;
+
/******************************************************************************/
})();
diff --git a/manifest.json b/manifest.json
index 4a926eed0..4bfcbb859 100644
--- a/manifest.json
+++ b/manifest.json
@@ -2,7 +2,7 @@
"manifest_version": 2,
"name": "__MSG_extName__",
"short_name": "µBlock",
- "version": "0.4.0.1",
+ "version": "0.4.0.2",
"description": "__MSG_extShortDesc__",
"icons": {
"16": "img/icon_16.png",