aboutsummaryrefslogtreecommitdiffstats
path: root/js
diff options
context:
space:
mode:
authorgorhill <rhill@raymondhill.net>2014-08-19 20:41:52 -0400
committergorhill <rhill@raymondhill.net>2014-08-19 20:41:52 -0400
commit1deae3bfe0c1379efb869b94a30d1a731292cfab (patch)
tree2154673232cac0c2bd58398c20c2bb12b42181dc /js
parent56ed6b9f4e65efb8b80b7b1ad93931484c54dde0 (diff)
downloaduBlock-1deae3bfe0c1379efb869b94a30d1a731292cfab.zip
uBlock-1deae3bfe0c1379efb869b94a30d1a731292cfab.tar.gz
uBlock-1deae3bfe0c1379efb869b94a30d1a731292cfab.tar.bz2
this fixes #138, next: thorough code review
Diffstat (limited to 'js')
-rw-r--r--js/3p-filters.js109
-rw-r--r--js/about.js88
-rw-r--r--js/asset-updater.js227
-rw-r--r--js/assets.js909
-rw-r--r--js/background.js1
-rw-r--r--js/messaging-handlers.js35
-rw-r--r--js/messaging.js9
-rw-r--r--js/pagestore.js4
-rw-r--r--js/start.js13
-rw-r--r--js/storage.js29
-rw-r--r--js/ublock.js21
-rw-r--r--js/utils.js36
12 files changed, 784 insertions, 697 deletions
diff --git a/js/3p-filters.js b/js/3p-filters.js
index ac18763..cb3da21 100644
--- a/js/3p-filters.js
+++ b/js/3p-filters.js
@@ -31,6 +31,7 @@ var userListName = chrome.i18n.getMessage('1pPageName');
var listDetails = {};
var externalLists = '';
var cacheWasPurged = false;
+var needUpdate = false;
/******************************************************************************/
@@ -40,7 +41,6 @@ var onMessage = function(msg) {
switch ( msg.what ) {
case 'loadUbiquitousBlacklistCompleted':
renderBlacklists();
- selectedBlacklistsChanged();
break;
default:
@@ -109,6 +109,8 @@ var renderBlacklists = function() {
var listStatsTemplate = chrome.i18n.getMessage('3pListsOfBlockedHostsPerListStats');
var purgeButtontext = chrome.i18n.getMessage('3pExternalListPurge');
+ var updateButtontext = chrome.i18n.getMessage('3pExternalListNew');
+ var obsoleteButtontext = chrome.i18n.getMessage('3pExternalListObsolete');
var htmlFromBranch = function(groupKey, listKeys, lists) {
var html = [
@@ -134,7 +136,7 @@ var renderBlacklists = function() {
listStatsTemplate,
'</span>'
].join('');
- var listKey, list, listEntry;
+ var listKey, list, listEntry, entryDetails;
for ( var i = 0; i < listKeys.length; i++ ) {
listKey = listKeys[i];
list = lists[listKey];
@@ -146,12 +148,27 @@ var renderBlacklists = function() {
.replace('{{total}}', !isNaN(+list.entryCount) ? renderNumber(list.entryCount) : '?');
html.push(listEntry);
// https://github.com/gorhill/uBlock/issues/104
- if ( /^https?:\/\/.+/.test(listKey) && listDetails.cache[listKey] ) {
+ entryDetails = listDetails.cache[listKey];
+ if ( entryDetails === undefined ) {
+ continue;
+ }
+ // Update status
+ if ( !list.off && (entryDetails.repoObsolete || entryDetails.cacheObsolete) ) {
+ html.push(
+ '&ensp;',
+ '<span class="status obsolete">',
+ entryDetails.repoObsolete ? updateButtontext : obsoleteButtontext,
+ '</span>'
+ );
+ needUpdate = true;
+ }
+ // In cache
+ else if ( entryDetails.cached ) {
html.push(
'&ensp;',
- '<button type="button" class="purge">',
+ '<span class="status purge">',
purgeButtontext,
- '</button>'
+ '</span>'
);
}
}
@@ -159,6 +176,8 @@ var renderBlacklists = function() {
return html.join('');
};
+ // https://www.youtube.com/watch?v=unCVi4hYRlY#t=30m18s
+
var groupsFromLists = function(lists) {
var groups = {};
var listKeys = Object.keys(lists);
@@ -178,6 +197,7 @@ var renderBlacklists = function() {
var onListsReceived = function(details) {
listDetails = details;
+ needUpdate = false;
var lists = details.available;
var html = [];
@@ -208,13 +228,10 @@ var renderBlacklists = function() {
uDom('#lists .listDetails').remove();
uDom('#lists').html(html.join(''));
- uDom('#parseAllABPHideFilters').prop('checked', listDetails.cosmetic === true);
- uDom('#ubiquitousParseAllABPHideFiltersPrompt2').text(
- chrome.i18n.getMessage("listsParseAllABPHideFiltersPrompt2")
- .replace('{{abpHideFilterCount}}', renderNumber(µb.abpHideFilters.getFilterCount()))
- );
+ uDom('#autoUpdate').prop('checked', listDetails.autoUpdate === true);
+ uDom('#parseCosmeticFilters').prop('checked', listDetails.cosmetic === true);
uDom('a').attr('target', '_blank');
- selectedBlacklistsChanged();
+ updateApplyButtons();
};
messaging.ask({ what: 'getLists' }, onListsReceived);
@@ -222,9 +239,9 @@ var renderBlacklists = function() {
/******************************************************************************/
-// Check whether lists need reloading.
+// Return whether selection of lists changed.
-var needToReload = function() {
+var listsSelectionChanged = function() {
if ( listDetails.cosmetic !== getµb().userSettings.parseAllABPHideFilters ) {
return true;
}
@@ -261,10 +278,19 @@ var needToReload = function() {
/******************************************************************************/
+// Return whether content need update.
+
+var listsContentChanged = function() {
+ return needUpdate;
+};
+
+/******************************************************************************/
+
// This is to give a visual hint that the selection of blacklists has changed.
-var selectedBlacklistsChanged = function() {
- uDom('#blacklistsApply').prop('disabled', !needToReload());
+var updateApplyButtons = function() {
+ uDom('#buttonApply').toggleClass('enabled', listsSelectionChanged());
+ uDom('#buttonUpdate').toggleClass('enabled', listsContentChanged());
};
/******************************************************************************/
@@ -278,7 +304,7 @@ var onListCheckboxChanged = function() {
return;
}
listDetails.available[href].off = !this.checked;
- selectedBlacklistsChanged();
+ updateApplyButtons();
};
/******************************************************************************/
@@ -295,22 +321,22 @@ var onListLinkClicked = function(ev) {
var onPurgeClicked = function(ev) {
var button = uDom(this);
- var href = button.parent().find('a').first().attr('href');
+ var li = button.parent();
+ var href = li.find('a').first().attr('href');
if ( !href ) {
return;
}
messaging.tell({ what: 'purgeCache', path: href });
button.remove();
- cacheWasPurged = true;
- selectedBlacklistsChanged();
+ if ( li.find('input').first().prop('checked') ) {
+ cacheWasPurged = true;
+ updateApplyButtons();
+ }
};
/******************************************************************************/
-var blacklistsApplyHandler = function() {
- if ( !needToReload() ) {
- return;
- }
+var reloadAll = function(update) {
// Reload blacklists
messaging.tell({
what: 'userSettings',
@@ -334,17 +360,40 @@ var blacklistsApplyHandler = function() {
}
messaging.tell({
what: 'reloadAllFilters',
- switches: switches
+ switches: switches,
+ update: update
});
cacheWasPurged = false;
- uDom('#blacklistsApply').prop('disabled', true);
+};
+
+/******************************************************************************/
+
+var buttonApplyHandler = function() {
+ reloadAll();
+ uDom('#buttonApply').toggleClass('enabled', false);
+};
+
+/******************************************************************************/
+
+var buttonUpdateHandler = function() {
+ reloadAll(true);
+};
+
+/******************************************************************************/
+
+var autoUpdateCheckboxChanged = function() {
+ messaging.tell({
+ what: 'userSettings',
+ name: 'autoUpdate',
+ value: this.checked
+ });
};
/******************************************************************************/
var abpHideFiltersCheckboxChanged = function() {
listDetails.cosmetic = this.checked;
- selectedBlacklistsChanged();
+ updateApplyButtons();
};
/******************************************************************************/
@@ -383,11 +432,13 @@ var externalListsApplyHandler = function() {
uDom.onLoad(function() {
// Handle user interaction
- uDom('#parseAllABPHideFilters').on('change', abpHideFiltersCheckboxChanged);
- uDom('#blacklistsApply').on('click', blacklistsApplyHandler);
+ uDom('#autoUpdate').on('change', autoUpdateCheckboxChanged);
+ uDom('#parseCosmeticFilters').on('change', abpHideFiltersCheckboxChanged);
+ uDom('#buttonApply').on('click', buttonApplyHandler);
+ uDom('#buttonUpdate').on('click', buttonUpdateHandler);
uDom('#lists').on('change', '.listDetails > input', onListCheckboxChanged);
uDom('#lists').on('click', '.listDetails > a:nth-of-type(1)', onListLinkClicked);
- uDom('#lists').on('click', 'button.purge', onPurgeClicked);
+ uDom('#lists').on('click', 'span.purge', onPurgeClicked);
uDom('#externalLists').on('input', externalListsChangeHandler);
uDom('#externalListsApply').on('click', externalListsApplyHandler);
diff --git a/js/about.js b/js/about.js
index b27cb7e..3203b98 100644
--- a/js/about.js
+++ b/js/about.js
@@ -27,95 +27,7 @@ uDom.onLoad(function() {
/******************************************************************************/
-var updateList = {};
-var assetListSwitches = ['o', 'o', 'o'];
-var commitHistoryURLPrefix = 'https://github.com/gorhill/ublock/commits/master/';
-
-/******************************************************************************/
-
-var setAssetListClassBit = function(bit, state) {
- assetListSwitches[assetListSwitches.length-1-bit] = !state ? 'o' : 'x';
- uDom('#assetList')
- .removeClass()
- .addClass(assetListSwitches.join(''));
-};
-
-/******************************************************************************/
-
-var renderAssetList = function(details) {
- var dirty = false;
- var paths = Object.keys(details.list).sort();
- if ( paths.length > 0 ) {
- uDom('#assetList .assetEntry').remove();
- var i = 0;
- var path, status, html = [];
- while ( path = paths[i++] ) {
- status = details.list[path].status;
- dirty = dirty || status !== 'Unchanged';
- html.push(
- '<tr class="assetEntry ' + status.toLowerCase().replace(/ +/g, '-') + '">',
- '<td>',
- '<a href="' + commitHistoryURLPrefix + path + '">',
- path.replace(/^(assets\/[^/]+\/)(.+)$/, '$1<b>$2</b>'),
- '</a>',
- '<td>',
- 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 546682b..0000000
--- 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 3f202d1..e57247c 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, '___');
+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);
+ }
+ }
+ 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 getRepoMeta = function(callback, update) {
+ callback = callback || nullFunc;
-var requestFileSystem = function(onSuccess, onError) {
- if ( fileSystem ) {
- onSuccess(fileSystem);
+ if ( update ) {
+ repoMeta = null;
+ }
+ if ( repoMeta !== null ) {
+ if ( repoMeta.waiting.length !== 0 ) {
+ repoMeta.waiting.push(callback);
+ } else {
+ callback(repoMeta);
+ }
return;
}
- 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);
-};
-
-/******************************************************************************/
-
-// https://github.com/gorhill/uBlock/issues/89
-// Remove when I am confident everybody moved to the new storage
-
-var oldReadCachedFile = function(path, callback) {
- var reportBack = function(content, err) {
- var details = {
- 'path': path,
- 'content': content,
- 'error': err
- };
- callback(details);
- };
-
- var onCacheFileLoaded = function() {
- // console.log('µBlock> readLocalFile() / onCacheFileLoaded()');
- reportBack(this.responseText);
- this.onload = this.onerror = null;
+ // 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);
+ }
};
- 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 pathToHomeURLs = 'assets/ublock/thirdparty-lists.txt';
- 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 onLocalHomeURLsLoaded = function(details) {
+ parseHomeURLs(details.content);
};
- var onCacheEntryError = function(err) {
- if ( err.name !== 'NotFoundError' ) {
- console.error('µBlock> readLocalFile() / onCacheEntryError("%s"):', path, err.name);
+ 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;
}
- reportBack('', 'Error');
- };
-
- var onRequestFileSystemSuccess = function(fs) {
- fs.root.getFile(cachePathFromPath(path), null, onCacheEntryFound, onCacheEntryError);
+ cachedAssetsManager.save(pathToHomeURLs, details.content, onLocalHomeURLsLoaded);
};
- 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;
- }
- cacheSynchronized = true;
-
- // https://github.com/gorhill/uBlock/issues/89
- // Remove when I am confident everybody moved to the new storage
+ var checksumCountdown = 2;
+ var localChecksums = '';
+ var repoChecksums = '';
- var directoryReader;
- var done = function() {
- directoryReader = null;
- };
-
- var onReadEntries = function(entries) {
- var n = entries.length;
- if ( !n ) {
- return done();
+ 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 i = 0; i < n; i++ ) {
- entry = entries[i];
- entry.remove(nullFunc);
+ 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);
}
- // 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 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 onLastVersionRead = function(store) {
- var currentVersion = chrome.runtime.getManifest().version;
- var lastVersion = store.extensionLastVersion || '0.0.0.0';
- if ( currentVersion === lastVersion ) {
- return done();
+ 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];
}
- 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 onLocalChecksumsLoaded = function(details) {
+ if ( localChecksums = validateChecksums(details) ) {
+ parseChecksums(localChecksums, 'local');
+ }
+ checksumsReceived();
};
- var onUserFiltersLoaded = function(details) {
- if ( details.content !== '' ) {
- cachedAssetsManager.save(details.path, details.content, onUserFiltersSaved);
- } else {
- chrome.storage.local.get('extensionLastVersion', onLastVersionRead);
+ var onRepoChecksumsLoaded = function(details) {
+ if ( repoChecksums = validateChecksums(details) ) {
+ parseChecksums(repoChecksums, 'repo');
}
+ checksumsReceived();
};
- oldReadCachedFile('assets/user/filters.txt', onUserFiltersLoaded);
+ 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,165 +511,521 @@ 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 writeLocalFile = function(path, content, callback) {
- cachedAssetsManager.save(path, content, callback);
-};
+ 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 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 || '';
+ 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 reportBackError = function() {
- callback({
- 'path': targetPath,
- 'error': 'Error'
- });
+ 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' ) {
- console.error('µBlock> updateFromRemote("%s") / onRepoFileLoaded(): no response', repositoryURL);
- reportBackError();
+ if ( typeof this.responseText !== 'string' || this.responseText === '' ) {
+ console.error('µBlock> readRepoCopyAsset("%s") / onRepoFileLoaded("%s"): no response', path, repositoryURL);
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
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);
+ console.debug('µBlock> readRepoCopyAsset("%s") / onRepoFileLoaded("%s")', path, repositoryURL);
+ updateChecksum();
+ cachedAssetsManager.save(path, this.responseText, callback);
};
- var onRepoFileError = function(ev) {
+ var onRepoFileError = function() {
this.onload = this.onerror = null;
- console.error('µBlock> updateFromRemote() / onRepoFileError("%s"):', repositoryURL, this.statusText);
- reportBackError();
+ 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' ) {
- console.error('µBlock> updateFromRemote("%s") / onHomeFileLoaded(): no response', homeURL);
- getTextFileFromURL(repositoryURL, onRepoFileLoaded, onRepoFileError);
+ 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;
}
- if ( targetMd5 !== '' && YaMD5.hashStr(this.responseText) !== targetMd5 ) {
- console.error('µBlock> updateFromRemote("%s") / onHomeFileLoaded(): bad md5 checksum', homeURL);
+ 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;
}
- // console.debug('µBlock> updateFromRemote("%s") / onHomeFileLoaded()', homeURL);
- writeLocalFile(targetPath, this.responseText, callback);
+ // Not in cache
+ getTextFileFromURL(chrome.runtime.getURL(path), onInstallFileLoaded, onInstallFileError);
};
- var onHomeFileError = function(ev) {
+ 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.error('µBlock> updateFromRemote() / onHomeFileError("%s"):', homeURL, this.statusText);
- getTextFileFromURL(repositoryURL, onRepoFileLoaded, onRepoFileError);
+ console.log('µBlock> readRepoOnlyAsset("%s") / onInstallFileLoaded()', path);
+ reportBack(this.responseText);
};
- // 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];
- }
+ 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 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);
+ 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;
}
- // 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);
+ console.debug('µBlock> readRepoOnlyAsset("%s") / onRepoFileLoaded("%s")', path, repositoryURL);
+ assetEntry.localChecksum = assetEntry.repoChecksum;
+ updateLocalChecksums();
+ cachedAssetsManager.save(path, this.responseText, callback);
};
- // 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();
+ 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;
}
- 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 ) {
- continue;
+
+ // 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;
}
- k = pair.slice(0, pos).trim();
- v = pair.slice(pos).trim();
- if ( k === '' || v === '' ) {
- continue;
+ }
+
+ // Asset unchanged: load from cache
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+ };
+
+ getRepoMeta(onRepoMetaReady);
+};
+
+/******************************************************************************/
+
+// 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;
+ }
}
- thirdpartyHomeURLs[k] = v;
+ // Not obsolete
+ cachedAssetsManager.load(path, onCachedContentLoaded, onCachedContentError);
+ return;
}
- getThirdpartyHomeURL();
+ // 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': '' });
};
- // Get home servers if not done yet.
- if ( thirdpartyHomeURLs === null ) {
- readLocalFile('assets/ublock/thirdparty-lists.txt', onThirdpartyHomeURLsLoaded);
- } else {
- getThirdpartyHomeURL();
+ 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
+
/******************************************************************************/
-var cacheEntries = function(callback) {
- return cachedAssetsManager.entries(callback);
+exports.put = function(path, content, callback) {
+ cachedAssetsManager.save(path, content, callback);
};
/******************************************************************************/
-// Flush cached assets if cache content is from an older version: the extension
-// always ships with the most up-to-date assets.
+exports.purge = function(pattern, before) {
+ cachedAssetsManager.remove(pattern, before);
+};
+
+/******************************************************************************/
+
+exports.metadata = function(callback) {
+ var out = {};
+
+ var onRepoMetaReady = function(meta) {
+ var entries = meta.entries;
+ var entryRepo, entryOut;
+ for ( var path in entries ) {
+ if ( entries.hasOwnProperty(path) === false ) {
+ continue;
+ }
+ entryRepo = entries[path];
+ entryOut = out[path];
+ if ( entryOut === undefined ) {
+ entryOut = out[path] = {};
+ }
+ entryOut.localChecksum = entryRepo.localChecksum;
+ entryOut.repoChecksum = entryRepo.repoChecksum;
+ entryOut.homeURL = entryRepo.homeURL;
+ entryOut.repoObsolete = entryOut.localChecksum !== entryOut.repoChecksum;
+ }
+ callback(out);
+ };
+
+ 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);
+ };
-synchronizeCache();
+ cachedAssetsManager.entries(onCacheMetaReady);
+};
/******************************************************************************/
-// Export API
+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);
+ };
-return {
- 'get': readLocalFile,
- 'getExternal': readExternalFile,
- 'getRepo': readRepoFile,
- 'purge': purgeCache,
- 'put': writeLocalFile,
- 'update': updateFromRemote,
- 'entries': cacheEntries
+ exports.metadata(onMetaDataReady);
};
/******************************************************************************/
+return exports;
+
+/******************************************************************************/
+
})();
/******************************************************************************/
diff --git a/js/background.js b/js/background.js
index 7c966fc..ff72b98 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 3a93f81..460896f 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 bc5d4e7..86c2309 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 d541305..1302155 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 2ab08ba..aef9f4a 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 1073d2c..a8f1840 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 169efbb..cce9aa5 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 0b534de..65cc997 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;
+
/******************************************************************************/
})();