diff options
author | Jesús <heckyel@hyperbola.info> | 2019-08-11 19:58:26 -0500 |
---|---|---|
committer | Jesús <heckyel@hyperbola.info> | 2019-08-11 19:58:26 -0500 |
commit | 9ec39f09621c9975582a2b6d9a6fa0313b308086 (patch) | |
tree | 918786fdf371606f80bc9cdfbc2966909703e9bf | |
parent | 144581a54b8bb1808e23a3ea5c81e619e36a459f (diff) | |
download | ematrix-9ec39f09621c9975582a2b6d9a6fa0313b308086.tar.lz ematrix-9ec39f09621c9975582a2b6d9a6fa0313b308086.tar.xz ematrix-9ec39f09621c9975582a2b6d9a6fa0313b308086.zip |
remove tabs in javascript files
43 files changed, 11107 insertions, 11055 deletions
diff --git a/js/about.js b/js/about.js index 233c3f0..098bb5e 100644 --- a/js/about.js +++ b/js/about.js @@ -29,119 +29,119 @@ uDom.onLoad(function() { -/******************************************************************************/ - -var backupUserDataToFile = function() { - var userDataReady = function(userData) { - vAPI.download({ - 'url': 'data:text/plain,' + encodeURIComponent(JSON.stringify(userData, null, 2)), - 'filename': uDom('[data-i18n="aboutBackupFilename"]').text() - }); - }; + /******************************************************************************/ - vAPI.messaging.send('about.js', { what: 'getAllUserData' }, userDataReady); -}; + var backupUserDataToFile = function() { + var userDataReady = function(userData) { + vAPI.download({ + 'url': 'data:text/plain,' + encodeURIComponent(JSON.stringify(userData, null, 2)), + 'filename': uDom('[data-i18n="aboutBackupFilename"]').text() + }); + }; -/******************************************************************************/ - -function restoreUserDataFromFile() { - var validateBackup = function(s) { - var userData = null; - try { - userData = JSON.parse(s); - } - catch (e) { - userData = null; - } - if ( userData === null ) { - return null; - } - if ( - typeof userData !== 'object' || - typeof userData.version !== 'string' || - typeof userData.when !== 'number' || - typeof userData.settings !== 'object' || - typeof userData.rules !== 'string' || - typeof userData.hostsFiles !== 'object' - ) { - return null; - } - return userData; + vAPI.messaging.send('about.js', { what: 'getAllUserData' }, userDataReady); }; - var fileReaderOnLoadHandler = function() { - var userData = validateBackup(this.result); - if ( !userData ) { - window.alert(uDom('[data-i18n="aboutRestoreError"]').text()); + /******************************************************************************/ + + function restoreUserDataFromFile() { + var validateBackup = function(s) { + var userData = null; + try { + userData = JSON.parse(s); + } + catch (e) { + userData = null; + } + if ( userData === null ) { + return null; + } + if ( + typeof userData !== 'object' || + typeof userData.version !== 'string' || + typeof userData.when !== 'number' || + typeof userData.settings !== 'object' || + typeof userData.rules !== 'string' || + typeof userData.hostsFiles !== 'object' + ) { + return null; + } + return userData; + }; + + var fileReaderOnLoadHandler = function() { + var userData = validateBackup(this.result); + if ( !userData ) { + window.alert(uDom('[data-i18n="aboutRestoreError"]').text()); + return; + } + var time = new Date(userData.when); + var msg = uDom('[data-i18n="aboutRestoreConfirm"]').text() + .replace('{{time}}', time.toLocaleString()); + var proceed = window.confirm(msg); + if ( proceed ) { + vAPI.messaging.send( + 'about.js', + { what: 'restoreAllUserData', userData: userData } + ); + } + }; + + var file = this.files[0]; + if ( file === undefined || file.name === '' ) { return; } - var time = new Date(userData.when); - var msg = uDom('[data-i18n="aboutRestoreConfirm"]').text() - .replace('{{time}}', time.toLocaleString()); - var proceed = window.confirm(msg); - if ( proceed ) { - vAPI.messaging.send( - 'about.js', - { what: 'restoreAllUserData', userData: userData } - ); + if ( file.type.indexOf('text') !== 0 ) { + return; } - }; - - var file = this.files[0]; - if ( file === undefined || file.name === '' ) { - return; + var fr = new FileReader(); + fr.onload = fileReaderOnLoadHandler; + fr.readAsText(file); } - if ( file.type.indexOf('text') !== 0 ) { - return; - } - var fr = new FileReader(); - fr.onload = fileReaderOnLoadHandler; - fr.readAsText(file); -} - -/******************************************************************************/ -var startRestoreFilePicker = function() { - var input = document.getElementById('restoreFilePicker'); - // Reset to empty string, this will ensure an change event is properly - // triggered if the user pick a file, even if it is the same as the last - // one picked. - input.value = ''; - input.click(); -}; + /******************************************************************************/ -/******************************************************************************/ + var startRestoreFilePicker = function() { + var input = document.getElementById('restoreFilePicker'); + // Reset to empty string, this will ensure an change event is properly + // triggered if the user pick a file, even if it is the same as the last + // one picked. + input.value = ''; + input.click(); + }; -var resetUserData = function() { - var proceed = window.confirm(uDom('[data-i18n="aboutResetConfirm"]').text()); - if ( proceed ) { - vAPI.messaging.send('about.js', { what: 'resetAllUserData' }); - } -}; + /******************************************************************************/ -/******************************************************************************/ - -(function() { - var renderStats = function(details) { - document.getElementById('aboutVersion').textContent = details.version; - var template = uDom('[data-i18n="aboutStorageUsed"]').text(); - var storageUsed = '?'; - if ( typeof details.storageUsed === 'number' ) { - storageUsed = details.storageUsed.toLocaleString(); + var resetUserData = function() { + var proceed = window.confirm(uDom('[data-i18n="aboutResetConfirm"]').text()); + if ( proceed ) { + vAPI.messaging.send('about.js', { what: 'resetAllUserData' }); } - document.getElementById('aboutStorageUsed').textContent = - template.replace('{{storageUsed}}', storageUsed); }; - vAPI.messaging.send('about.js', { what: 'getSomeStats' }, renderStats); -})(); - -/******************************************************************************/ - -uDom('#backupUserDataButton').on('click', backupUserDataToFile); -uDom('#restoreUserDataButton').on('click', startRestoreFilePicker); -uDom('#restoreFilePicker').on('change', restoreUserDataFromFile); -uDom('#resetUserDataButton').on('click', resetUserData); -/******************************************************************************/ + /******************************************************************************/ + + (function() { + var renderStats = function(details) { + document.getElementById('aboutVersion').textContent = details.version; + var template = uDom('[data-i18n="aboutStorageUsed"]').text(); + var storageUsed = '?'; + if ( typeof details.storageUsed === 'number' ) { + storageUsed = details.storageUsed.toLocaleString(); + } + document.getElementById('aboutStorageUsed').textContent = + template.replace('{{storageUsed}}', storageUsed); + }; + vAPI.messaging.send('about.js', { what: 'getSomeStats' }, renderStats); + })(); + + /******************************************************************************/ + + uDom('#backupUserDataButton').on('click', backupUserDataToFile); + uDom('#restoreUserDataButton').on('click', startRestoreFilePicker); + uDom('#restoreFilePicker').on('change', restoreUserDataFromFile); + uDom('#resetUserDataButton').on('click', resetUserData); + + /******************************************************************************/ }); diff --git a/js/assets.js b/js/assets.js index 595b681..ec06c62 100644 --- a/js/assets.js +++ b/js/assets.js @@ -33,21 +33,21 @@ let connectionError = vAPI.i18n('errorCantConnectTo'); let notifyObservers = function (topic, details) { - let result; + let result; - for (let i=0; i<observers.length; ++i) { - result = observers[i](topic, details); - } + for (let i=0; i<observers.length; ++i) { + result = observers[i](topic, details); + } - return result; + return result; } function isEmptyString(s) { - return (typeof s === 'string' && s === ''); + return (typeof s === 'string' && s === ''); } function noOp() { - return; + return; } // Cache Registry @@ -57,308 +57,308 @@ let cacheRegistryStart = Date.now(); let saveCacheRegistry = (function () { - let timer; - - function save() { - timer = undefined; - vAPI.cacheStorage.set({ - assetCacheRegistry: cacheRegistry, - }); - } - - return function (lazy) { - if (timer !== undefined) { - clearTimeout(timer); - } - - if (lazy === true) { - timer = vAPI.setTimeout(save, 500); - } else { - save(); - } - }; + let timer; + + function save() { + timer = undefined; + vAPI.cacheStorage.set({ + assetCacheRegistry: cacheRegistry, + }); + } + + return function (lazy) { + if (timer !== undefined) { + clearTimeout(timer); + } + + if (lazy === true) { + timer = vAPI.setTimeout(save, 500); + } else { + save(); + } + }; })(); let getCacheRegistry = function (callback) { - if (cacheRegistryReady == true) { - callback(cacheRegistry); - return; - } - - if (cacheRegistryCallbacks !== undefined) { - // If it's not undefined it's always an array - // - // eMatrix: this block in particular is probably never - // called: originally, it was used because uMatrix called - // some code in a callback that could wait - // - // While waiting, more elements could've been pushed in - // the array - // - // Since the waiting callback is not used here, any - // further callback are likely to be handled by the - // condition above - // This block is left here just in case - cacheRegistryCallbacks.push(callback); - return; - } - - cacheRegistryCallbacks = [callback]; - cacheRegistryReady = true; - - let onRead = function (bin) { - if (!bin || !bin['assetCacheRegistry']) { - cacheRegistry = {}; - } else { - cacheRegistry = bin['assetCacheRegistry']; - } - }; - - vAPI.cacheStorage.get('assetCacheRegistry', onRead); - - let f; - while ((f = cacheRegistryCallbacks.shift())) { - f(cacheRegistry); - } + if (cacheRegistryReady == true) { + callback(cacheRegistry); + return; + } + + if (cacheRegistryCallbacks !== undefined) { + // If it's not undefined it's always an array + // + // eMatrix: this block in particular is probably never + // called: originally, it was used because uMatrix called + // some code in a callback that could wait + // + // While waiting, more elements could've been pushed in + // the array + // + // Since the waiting callback is not used here, any + // further callback are likely to be handled by the + // condition above + // This block is left here just in case + cacheRegistryCallbacks.push(callback); + return; + } + + cacheRegistryCallbacks = [callback]; + cacheRegistryReady = true; + + let onRead = function (bin) { + if (!bin || !bin['assetCacheRegistry']) { + cacheRegistry = {}; + } else { + cacheRegistry = bin['assetCacheRegistry']; + } + }; + + vAPI.cacheStorage.get('assetCacheRegistry', onRead); + + let f; + while ((f = cacheRegistryCallbacks.shift())) { + f(cacheRegistry); + } }; let readCache = function (key, callback) { - let report = function (content, error) { - let details = { - assetKey: key, - content: content, - }; - - if (error) { - details.error = error; - } - - callback(details); - }; - - let onRead = function (bin) { - if (!bin || !bin[key]) { - report('', 'E_NOTFOUND'); - return; - } - - let entry = cacheRegistry[key]; - if (entry === undefined) { - let onRead2 = function (bin) { - if (!bin || !bin['assetCacheRegistry']) { - cacheRegistry = {}; - } else { - cacheRegistry = bin['assetCacheRegistry']; - } - }; - - vAPI.cacheStorage.get('assetCacheRegistry', onRead2); - - entry = cacheRegistry[key]; - if (entry === undefined) { - report('', 'E_NOTFOUND'); - return; - } - } - - entry.readTime = Date.now(); - saveCacheRegistry(true); - - report(bin[key]); - }; - - let onReady = function () { - vAPI.cacheStorage.get(key, onRead); - }; - - getCacheRegistry(onReady); + let report = function (content, error) { + let details = { + assetKey: key, + content: content, + }; + + if (error) { + details.error = error; + } + + callback(details); + }; + + let onRead = function (bin) { + if (!bin || !bin[key]) { + report('', 'E_NOTFOUND'); + return; + } + + let entry = cacheRegistry[key]; + if (entry === undefined) { + let onRead2 = function (bin) { + if (!bin || !bin['assetCacheRegistry']) { + cacheRegistry = {}; + } else { + cacheRegistry = bin['assetCacheRegistry']; + } + }; + + vAPI.cacheStorage.get('assetCacheRegistry', onRead2); + + entry = cacheRegistry[key]; + if (entry === undefined) { + report('', 'E_NOTFOUND'); + return; + } + } + + entry.readTime = Date.now(); + saveCacheRegistry(true); + + report(bin[key]); + }; + + let onReady = function () { + vAPI.cacheStorage.get(key, onRead); + }; + + getCacheRegistry(onReady); }; let writeCache = function (key, details, callback) { - let content = ''; - - if (typeof details === 'string') { - content = details; - } else if (details instanceof Object) { - content = details.content || ''; - } - - if (content === '') { - removeCache(key, callback); - return; - } - - let report = function (content) { - let details = { - assetKey: key, - content: content, - }; - - if (typeof callback === 'function') { - callback(details); - } - - notifyObservers('after-asset-updated', details); - }; - - let onReady = function () { - let entry = cacheRegistry[key]; - if (entry === undefined) { - entry = cacheRegistry[key] = {}; - } - - entry.writeTime = entry.readTime = Date.now(); - if (details instanceof Object && typeof details.url === 'string') { - entry.remoteURL = details.url; - } - - let bin = { - assetCacheRegistry: cacheRegistry, - }; - - bin[key] = content; - vAPI.cacheStorage.set(bin); - report(content); - }; - - getCacheRegistry(onReady); + let content = ''; + + if (typeof details === 'string') { + content = details; + } else if (details instanceof Object) { + content = details.content || ''; + } + + if (content === '') { + removeCache(key, callback); + return; + } + + let report = function (content) { + let details = { + assetKey: key, + content: content, + }; + + if (typeof callback === 'function') { + callback(details); + } + + notifyObservers('after-asset-updated', details); + }; + + let onReady = function () { + let entry = cacheRegistry[key]; + if (entry === undefined) { + entry = cacheRegistry[key] = {}; + } + + entry.writeTime = entry.readTime = Date.now(); + if (details instanceof Object && typeof details.url === 'string') { + entry.remoteURL = details.url; + } + + let bin = { + assetCacheRegistry: cacheRegistry, + }; + + bin[key] = content; + vAPI.cacheStorage.set(bin); + report(content); + }; + + getCacheRegistry(onReady); }; let cacheRemove = function (pattern, callback) { - let onReady = function (cache) { - let entries = []; + let onReady = function (cache) { + let entries = []; - for (let key in cache) { - if (pattern instanceof RegExp && !pattern.test(key)) { - continue; - } + for (let key in cache) { + if (pattern instanceof RegExp && !pattern.test(key)) { + continue; + } - if (typeof pattern === 'string' && key !== pattern) { - continue; - } + if (typeof pattern === 'string' && key !== pattern) { + continue; + } - entries.push(key); + entries.push(key); - delete cache[key]; - } + delete cache[key]; + } - if (entries.length !== 0) { - vAPI.cacheStorage.remove(content); + if (entries.length !== 0) { + vAPI.cacheStorage.remove(content); - let bin = { - assetCacheRegistry: cache, - }; - vAPI.cacheStorage.set(bin); - } + let bin = { + assetCacheRegistry: cache, + }; + vAPI.cacheStorage.set(bin); + } - if (typeof callback === 'function') { - callback(); - } + if (typeof callback === 'function') { + callback(); + } - for (let i=0; i<entries.length; ++i) { - notifyObservers('after-asset-updated', { - assetKey: entries[i], - }); - } - }; + for (let i=0; i<entries.length; ++i) { + notifyObservers('after-asset-updated', { + assetKey: entries[i], + }); + } + }; - getCacheRegistry(onReady); + getCacheRegistry(onReady); }; let markDirtyCache = function (pattern, exclude, callback) { - let onReady = function (registry) { - let entry; - let mustSave = false; - - for (let key in registry) { - if (pattern instanceof RegExp && pattern.test(key) === false) { - continue; - } else if (typeof pattern === 'string' && key !== pattern) { - continue; - } else if (Array.isArray(pattern) - && pattern.indexOf(key) === -1) { - continue; - } - - if (exclude instanceof RegExp && exclude.test(key)) { - continue; - } else if (typeof exclude === 'string' && key === exclude) { - continue; - } else if (Array.isArray(exclude) - && exclude.indexOf(key) !== -1) { - continue; - } - - entry = registry[key]; - if (!entry.writeTime) { - continue; - } - - registry[key].writeTime = 0; - mustSave = true; - } - - if (mustSave) { - let bin = { - assetCacheRegistry: registry, - }; - vAPI.cacheStorage.set(bin); - } - - if (typeof callback === 'function') { - callback(); - } - }; - - if (typeof exclude === 'function') { - callback = exclude; - exclude = undefined; - } - - getCacheRegistry(onReady); + let onReady = function (registry) { + let entry; + let mustSave = false; + + for (let key in registry) { + if (pattern instanceof RegExp && pattern.test(key) === false) { + continue; + } else if (typeof pattern === 'string' && key !== pattern) { + continue; + } else if (Array.isArray(pattern) + && pattern.indexOf(key) === -1) { + continue; + } + + if (exclude instanceof RegExp && exclude.test(key)) { + continue; + } else if (typeof exclude === 'string' && key === exclude) { + continue; + } else if (Array.isArray(exclude) + && exclude.indexOf(key) !== -1) { + continue; + } + + entry = registry[key]; + if (!entry.writeTime) { + continue; + } + + registry[key].writeTime = 0; + mustSave = true; + } + + if (mustSave) { + let bin = { + assetCacheRegistry: registry, + }; + vAPI.cacheStorage.set(bin); + } + + if (typeof callback === 'function') { + callback(); + } + }; + + if (typeof exclude === 'function') { + callback = exclude; + exclude = undefined; + } + + getCacheRegistry(onReady); }; let removeCache = function (key, callback) { - let onReady = function (cacheRegistry) { - let removedEntries = []; - let removedContent = []; - - for (let k in cacheRegistry) { - if (key instanceof RegExp && !key.test(k)) { - continue; - } - if (typeof key === 'string' && k !== key) { - continue; - } - - removedEntries.push(k); - removedContent.push(k); - - delete cacheRegistry[k]; - } - - if (removedContent.length > 0) { - vAPI.cacheStorage.remove(removedContent); - - let bin = { - assetCacheRegistry: cacheRegistry, - }; - - vAPI.cacheStorage.set(bin); - } - - if (typeof callback === 'function') { - callback(); - } - - for (let i=0; i<removedEntries.length; ++i) { - notifyObservers('after-asset-updated', { - assetKey: removedEntries[i], - }); - } - }; - - getCacheRegistry(onReady); + let onReady = function (cacheRegistry) { + let removedEntries = []; + let removedContent = []; + + for (let k in cacheRegistry) { + if (key instanceof RegExp && !key.test(k)) { + continue; + } + if (typeof key === 'string' && k !== key) { + continue; + } + + removedEntries.push(k); + removedContent.push(k); + + delete cacheRegistry[k]; + } + + if (removedContent.length > 0) { + vAPI.cacheStorage.remove(removedContent); + + let bin = { + assetCacheRegistry: cacheRegistry, + }; + + vAPI.cacheStorage.set(bin); + } + + if (typeof callback === 'function') { + callback(); + } + + for (let i=0; i<removedEntries.length; ++i) { + notifyObservers('after-asset-updated', { + assetKey: removedEntries[i], + }); + } + }; + + getCacheRegistry(onReady); }; // Source Registry @@ -367,246 +367,246 @@ let sourceRegistryCallbacks = undefined; let saveSourceRegistry = (function () { - let timer; - - function save() { - timer = undefined; - vAPI.cacheStorage.set({ - assetSourceRegistry: sourceRegistry, - }); - } - - return function (lazy) { - if (timer !== undefined) { - clearTimeout(timer); - } - - if (lazy === true) { - timer = vAPI.setTimeout(save, 500); - } else { - save(); - } - }; + let timer; + + function save() { + timer = undefined; + vAPI.cacheStorage.set({ + assetSourceRegistry: sourceRegistry, + }); + } + + return function (lazy) { + if (timer !== undefined) { + clearTimeout(timer); + } + + if (lazy === true) { + timer = vAPI.setTimeout(save, 500); + } else { + save(); + } + }; })(); let registerSource = function (key, details) { - let entry = sourceRegistry[key] || {}; - - for (let p in details) { - if (details.hasOwnProperty(p) === false) { - continue; - } - - if (details[p] !== undefined) { - entry[p] = details[p]; - } else { - delete entry[p]; - } - } - - let contentUrl = details.contentURL; - if (contentUrl) { - if (typeof contentUrl === 'string') { - contentUrl = entry.contentURL = [contentUrl]; - } else if (Array.isArray(contentUrl) === false) { - contentUrl = entry.contentURL = []; - } - - let remoteCount = 0; - - for (let i=0; i<contentUrl.length; ++i) { - if (externalPathRegex.test(contentUrl[i])) { - ++remoteCount; - } - } - - entry.hasLocalURL = (remoteCount !== contentUrl.length); - entry.hasRemoteURL = (remoteCount !== 0); - } else { - entry.contentURL = []; - } - - if (typeof entry.updateAfter !== 'number') { - entry.updateAfter = 13; - } - - if (entry.submitter) { - entry.submitTime = Date.now(); // Detects stale entries - } - - sourceRegistry[key] = entry; + let entry = sourceRegistry[key] || {}; + + for (let p in details) { + if (details.hasOwnProperty(p) === false) { + continue; + } + + if (details[p] !== undefined) { + entry[p] = details[p]; + } else { + delete entry[p]; + } + } + + let contentUrl = details.contentURL; + if (contentUrl) { + if (typeof contentUrl === 'string') { + contentUrl = entry.contentURL = [contentUrl]; + } else if (Array.isArray(contentUrl) === false) { + contentUrl = entry.contentURL = []; + } + + let remoteCount = 0; + + for (let i=0; i<contentUrl.length; ++i) { + if (externalPathRegex.test(contentUrl[i])) { + ++remoteCount; + } + } + + entry.hasLocalURL = (remoteCount !== contentUrl.length); + entry.hasRemoteURL = (remoteCount !== 0); + } else { + entry.contentURL = []; + } + + if (typeof entry.updateAfter !== 'number') { + entry.updateAfter = 13; + } + + if (entry.submitter) { + entry.submitTime = Date.now(); // Detects stale entries + } + + sourceRegistry[key] = entry; }; let unregisterSource = function (key) { - removeCache(key); - delete sourceRegistry[key]; - saveSourceRegistry(); + removeCache(key); + delete sourceRegistry[key]; + saveSourceRegistry(); }; let updateSourceRegistry = function (string, silent) { - let json; - - try { - json = JSON.parse(string); - } catch (e) { - return; - } - - for (let key in sourceRegistry) { - if (json[key] === undefined - && sourceRegistry[key].submitter === undefined) { - unregisterSource(key); - } - } - - for (let key in json) { - if (sourceRegistry[key] === undefined && !silent) { - notifyObservers('builtin-asset-source-added', { - assetKey: key, - entry: json[key], - }); - } - - registerSource(key, json[key]); - } - - saveSourceRegistry(); + let json; + + try { + json = JSON.parse(string); + } catch (e) { + return; + } + + for (let key in sourceRegistry) { + if (json[key] === undefined + && sourceRegistry[key].submitter === undefined) { + unregisterSource(key); + } + } + + for (let key in json) { + if (sourceRegistry[key] === undefined && !silent) { + notifyObservers('builtin-asset-source-added', { + assetKey: key, + entry: json[key], + }); + } + + registerSource(key, json[key]); + } + + saveSourceRegistry(); }; let getSourceRegistry = function (callback) { - if (sourceRegistryReady === true) { - callback(sourceRegistry); - return; - } - - if (sourceRegistryCallbacks !== undefined) { - // If it's not undefined it's always an array - sourceRegistryCallbacks.push(callback); - return; - } - - sourceRegistryCallbacks = [callback]; - - let onReady = function () { - sourceRegistryReady = true; - - let f; - while ((f = sourceRegistryCallbacks.shift())) { - f(sourceRegistry); - } - }; - - let createRegistry = function () { - api.fetchText - (ηMatrix.assetsBootstrapLocation || 'assets/assets.json', - function (details) { - updateSourceRegistry(details.content, true); - onReady(); - }); - }; - - let onRead = function (bin) { - if (!bin || !bin.assetSourceRegistry - || Object.keys(bin.assetSourceRegistry).length == 0) { - createRegistry(); - return; - } - - sourceRegistry = bin.assetSourceRegistry; - onReady(); - }; - - vAPI.cacheStorage.get('assetSourceRegistry', onRead); + if (sourceRegistryReady === true) { + callback(sourceRegistry); + return; + } + + if (sourceRegistryCallbacks !== undefined) { + // If it's not undefined it's always an array + sourceRegistryCallbacks.push(callback); + return; + } + + sourceRegistryCallbacks = [callback]; + + let onReady = function () { + sourceRegistryReady = true; + + let f; + while ((f = sourceRegistryCallbacks.shift())) { + f(sourceRegistry); + } + }; + + let createRegistry = function () { + api.fetchText + (ηMatrix.assetsBootstrapLocation || 'assets/assets.json', + function (details) { + updateSourceRegistry(details.content, true); + onReady(); + }); + }; + + let onRead = function (bin) { + if (!bin || !bin.assetSourceRegistry + || Object.keys(bin.assetSourceRegistry).length == 0) { + createRegistry(); + return; + } + + sourceRegistry = bin.assetSourceRegistry; + onReady(); + }; + + vAPI.cacheStorage.get('assetSourceRegistry', onRead); }; // Remote let getRemote = function (key, callback) { - let assetDetails = {}; - let contentUrl; - - let report = function (content, error) { - let details = { - assetKey: key, - content: content, - }; - if (error) { - details.error = assetDetails.lastError = error; - } - callback(details); - }; - - let tryLoad = function (tries) { - let urls = []; - - let tr = (tries === undefined) ? 10 : tries; - - if (tr <= 0) { - console.warn('ηMatrix could not load the asset ' - +assetDetails.title); - return; - } - - if (typeof assetDetails.contentURL === 'string') { - urls = [assetDetails.contentURL]; - } else if (Array.isArray(assetDetails.contentURL)) { - urls = assetDetails.contentURL.slice(0); - } - - while ((contentUrl = urls.shift())) { - if (externalPathRegex.test(contentUrl)) { - break; - } - } - - if (!contentUrl) { - report('', 'E_NOTFOUND'); - return; - } - - api.fetchText(contentUrl, onRemoteContentLoad, onRemoteContentError, - tr-1); - }; - - let onRemoteContentLoad = function (details, tries) { - if (isEmptyString(details.content) === true) { - registerSource(key, { - error: { - time: Date.now(), - error: 'No content' - } - }); - tryLoad(tries); - } - - writeCache(key, { - content: details.content, - url: contentUrl, - }); - - registerSource(key, {error: undefined}); - report(details.content); - }; - - let onRemoteContentError = function (details, tries) { - let text = detail.statusText; - if (details.statusCode === 0) { - text = 'network error'; - } - registerSource(key, { - error: { - time: Date.now(), - error: text, - } - }); - tryLoad(tries); - }; - - let onReady = function (registry) { - assetDetails = registry[key] || {}; - tryLoad(); - }; - - getSourceRegistry(onReady); + let assetDetails = {}; + let contentUrl; + + let report = function (content, error) { + let details = { + assetKey: key, + content: content, + }; + if (error) { + details.error = assetDetails.lastError = error; + } + callback(details); + }; + + let tryLoad = function (tries) { + let urls = []; + + let tr = (tries === undefined) ? 10 : tries; + + if (tr <= 0) { + console.warn('ηMatrix could not load the asset ' + +assetDetails.title); + return; + } + + if (typeof assetDetails.contentURL === 'string') { + urls = [assetDetails.contentURL]; + } else if (Array.isArray(assetDetails.contentURL)) { + urls = assetDetails.contentURL.slice(0); + } + + while ((contentUrl = urls.shift())) { + if (externalPathRegex.test(contentUrl)) { + break; + } + } + + if (!contentUrl) { + report('', 'E_NOTFOUND'); + return; + } + + api.fetchText(contentUrl, onRemoteContentLoad, onRemoteContentError, + tr-1); + }; + + let onRemoteContentLoad = function (details, tries) { + if (isEmptyString(details.content) === true) { + registerSource(key, { + error: { + time: Date.now(), + error: 'No content' + } + }); + tryLoad(tries); + } + + writeCache(key, { + content: details.content, + url: contentUrl, + }); + + registerSource(key, {error: undefined}); + report(details.content); + }; + + let onRemoteContentError = function (details, tries) { + let text = detail.statusText; + if (details.statusCode === 0) { + text = 'network error'; + } + registerSource(key, { + error: { + time: Date.now(), + error: text, + } + }); + tryLoad(tries); + }; + + let onReady = function (registry) { + assetDetails = registry[key] || {}; + tryLoad(); + }; + + getSourceRegistry(onReady); }; // Updater @@ -618,385 +618,385 @@ let updateFetch = new Set(); let updateStart = function () { - updateStatus = 'running'; - updateFetch.clear(); - updated = []; - notifyObservers('before-assets-updated'); - updateNext(); + updateStatus = 'running'; + updateFetch.clear(); + updated = []; + notifyObservers('before-assets-updated'); + updateNext(); }; let updateNext = function () { - let gcOne = function (key) { - let entry = cacheRegistry[key]; - if (entry && entry.readTime < cacheRegistryStart) { - cacheRemove(key); - } - }; - - let findOne = function () { - let now = Date.now(); - let sourceEntry; - let cacheEntry; - - for (let key in sourceRegistry) { - sourceEntry = sourceRegistry[key]; - if (sourceEntry.hasRemoteURL !== true) { - continue; - } - if (updateFetch.has(key)) { - continue; - } - - cacheEntry = cacheRegistry[key]; - if (cacheEntry - && (cacheEntry.writeTime - + sourceEntry.updateAfter*86400000) > now) { - continue; - } - if (notifyObservers('before-asset-updated', {assetKey: key})) { - return key; - } - - gcOne(key); - } - - return undefined; - }; - - let onUpdate = function (details) { - if (details.content !== '') { - updated.push(details.assetKey); - if (details.assetKey === 'asset.json') { - updateSourceRegistry(details.content); - } - } else { - notifyObservers('asset-update-failed', { - assetKey: details.assetKey, - }); - } - - if (findOne() !== undefined) { - vAPI.setTimeout(updateNext, updateDelay); - } else { - updateEnd(); - } - }; - - let updateOne = function () { - let key = findOne(); - if (key === undefined) { - updateEnd(); - return; - } - - updateFetch.add(key); - getRemote(key, onUpdate); - }; - - let onSourceReady = function (registry) { - updateOne(); - }; - - let onCacheReady = function (registry) { - getSourceRegistry(onSourceReady); - }; - - getCacheRegistry(onCacheReady); + let gcOne = function (key) { + let entry = cacheRegistry[key]; + if (entry && entry.readTime < cacheRegistryStart) { + cacheRemove(key); + } + }; + + let findOne = function () { + let now = Date.now(); + let sourceEntry; + let cacheEntry; + + for (let key in sourceRegistry) { + sourceEntry = sourceRegistry[key]; + if (sourceEntry.hasRemoteURL !== true) { + continue; + } + if (updateFetch.has(key)) { + continue; + } + + cacheEntry = cacheRegistry[key]; + if (cacheEntry + && (cacheEntry.writeTime + + sourceEntry.updateAfter*86400000) > now) { + continue; + } + if (notifyObservers('before-asset-updated', {assetKey: key})) { + return key; + } + + gcOne(key); + } + + return undefined; + }; + + let onUpdate = function (details) { + if (details.content !== '') { + updated.push(details.assetKey); + if (details.assetKey === 'asset.json') { + updateSourceRegistry(details.content); + } + } else { + notifyObservers('asset-update-failed', { + assetKey: details.assetKey, + }); + } + + if (findOne() !== undefined) { + vAPI.setTimeout(updateNext, updateDelay); + } else { + updateEnd(); + } + }; + + let updateOne = function () { + let key = findOne(); + if (key === undefined) { + updateEnd(); + return; + } + + updateFetch.add(key); + getRemote(key, onUpdate); + }; + + let onSourceReady = function (registry) { + updateOne(); + }; + + let onCacheReady = function (registry) { + getSourceRegistry(onSourceReady); + }; + + getCacheRegistry(onCacheReady); }; let updateEnd = function () { - let keys = updated.slice(0); - updateFetch.clear(); - updateStatus = 'stop'; - updateDelay = updateDefaultDelay; - notifyObservers('after-asset-updated', { - assetKeys: keys, - }); + let keys = updated.slice(0); + updateFetch.clear(); + updateStatus = 'stop'; + updateDelay = updateDefaultDelay; + notifyObservers('after-asset-updated', { + assetKeys: keys, + }); }; // Assets API api.addObserver = function (observer) { - if (observers.indexOf(observer) === -1) { - observers.push(observer); - } + if (observers.indexOf(observer) === -1) { + observers.push(observer); + } }; api.removeObserver = function (observer) { - let pos = observers.indexOf(observer); - if (pos !== -1) { - observers.splice(pos, 1); - } + let pos = observers.indexOf(observer); + if (pos !== -1) { + observers.splice(pos, 1); + } }; api.fetchText = function (url, onLoad, onError, tries) { - let iurl = externalPathRegex.test(url) ? url : vAPI.getURL(url); - let tr = (tries === undefined) ? 10 : tries; - - if (typeof onError !== 'function') { - onError = onLoad; - } - - let onResponseReceived = function () { - this.onload = this.onerror = this.ontimeout = null; - - let details = { - url: url, - content: '', - // On local files this.status is 0, but the request - // is successful - statusCode: this.status || 200, - statusText: this.statusText || '', - }; - - if (details.statusCode < 200 || details.statusCode >= 300) { - onError.call(null, details, tr); - return; - } - - if (isEmptyString(this.responseText) === true) { - onError.call(null, details, tr); - return; - } - - let t = this.responseText.trim(); - - // Discard HTML as it's probably an error - // (the request expects plain text as a response) - if (t.startsWith('<') && t.endsWith('>')) { - onError.call(null, details, tr); - return; - } - - details.content = t; - onLoad.call(null, details, tr); - }; - - let onErrorReceived = function () { - this.onload = this.onerror = this.ontimeout = null; - - ηMatrix.logger.writeOne('', 'error', - connectionError.replace('{{url}}', iurl)); - - onError.call(null, { - url: url, - content: '', - }, tr); - }; - - let req = new XMLHttpRequest(); - req.open('GET', iurl, true); - req.timeout = 30000; - req.onload = onResponseReceived; - req.onerror = onErrorReceived; - req.ontimeout = onErrorReceived; - req.responseType = 'text'; - - try { - // This can throw in some cases - req.send(); - } catch (e) { - onErrorReceived.call(req); - } + let iurl = externalPathRegex.test(url) ? url : vAPI.getURL(url); + let tr = (tries === undefined) ? 10 : tries; + + if (typeof onError !== 'function') { + onError = onLoad; + } + + let onResponseReceived = function () { + this.onload = this.onerror = this.ontimeout = null; + + let details = { + url: url, + content: '', + // On local files this.status is 0, but the request + // is successful + statusCode: this.status || 200, + statusText: this.statusText || '', + }; + + if (details.statusCode < 200 || details.statusCode >= 300) { + onError.call(null, details, tr); + return; + } + + if (isEmptyString(this.responseText) === true) { + onError.call(null, details, tr); + return; + } + + let t = this.responseText.trim(); + + // Discard HTML as it's probably an error + // (the request expects plain text as a response) + if (t.startsWith('<') && t.endsWith('>')) { + onError.call(null, details, tr); + return; + } + + details.content = t; + onLoad.call(null, details, tr); + }; + + let onErrorReceived = function () { + this.onload = this.onerror = this.ontimeout = null; + + ηMatrix.logger.writeOne('', 'error', + connectionError.replace('{{url}}', iurl)); + + onError.call(null, { + url: url, + content: '', + }, tr); + }; + + let req = new XMLHttpRequest(); + req.open('GET', iurl, true); + req.timeout = 30000; + req.onload = onResponseReceived; + req.onerror = onErrorReceived; + req.ontimeout = onErrorReceived; + req.responseType = 'text'; + + try { + // This can throw in some cases + req.send(); + } catch (e) { + onErrorReceived.call(req); + } }; api.registerAssetSource = function (key, details) { - getSourceRegistry(function () { - registerSource(key, details); - saveSourceRegistry(true); - }); + getSourceRegistry(function () { + registerSource(key, details); + saveSourceRegistry(true); + }); }; api.unregisterAssetSource = function (key) { - getSourceRegistry(function () { - unregisterSource(key); - saveSourceRegistry(true); - }); + getSourceRegistry(function () { + unregisterSource(key); + saveSourceRegistry(true); + }); }; api.get = function (key, options, callback) { - let cb; - let opt; - - if (typeof options === 'function') { - cb = options; - opt = {}; - } else if (typeof callback !== 'function') { - cb = noOp; - opt = options; - } else { - cb = callback; - opt = options; - } - - let assetDetails = {}; - let contentUrl = undefined; - - let report = function (content, error) { - let details = { - assetKey: key, - content: content, - }; - - if (error) { - details.error = assetDetails.error = error; - } else { - assetDetails.error = undefined; - } - - cb(details); - }; - - let onContentNotLoaded = function (details, tries) { - let external; - let urls = []; - - let tr = (tries === undefined) ? 10 : tries; - - if (tr <= 0) { - console.warn('ηMatrix couldn\'t download the asset ' - +assetDetails.title); - return; - } - - if (typeof assetDetails.contentURL === 'string') { - urls = [assetDetails.contentURL]; - } else if (Array.isArray(assetDetails.contentURL)) { - urls = assetDetails.contentURL.slice(0); - } - - while ((contentUrl = urls.shift())) { - external = externalPathRegex.test(contentUrl); - if (external === true && assetDetails.loaded !== true) { - break; - } - if (external === false || assetDetails.hasLocalURL !== true) { - break; - } - } - - if (!contentUrl) { - report('', 'E_NOTFOUND'); - return; - } - - api.fetchText(contentUrl, onContentLoaded, onContentNotLoaded, - tr-1); - }; - - let onContentLoaded = function (details, tries) { - if (isEmptyString(details.content) === true) { - onContentNotLoaded(undefined, tries); - return; - } - - if (externalPathRegex.test(details.url) - && opt.dontCache !== true) { - writeCache(key, { - content: details.content, - url: contentUrl, - }); - } - - assetDetails.loaded = true; - - report(details.content); - }; - - let onCachedContentLoad = function (details) { - if (details.content !== '') { - report(details.content); - return; - } - - let onReady = function (registry) { - assetDetails = registry[key] || {}; - onContentNotLoaded(); - } - - getSourceRegistry(onReady); - }; - - readCache(key, onCachedContentLoad); + let cb; + let opt; + + if (typeof options === 'function') { + cb = options; + opt = {}; + } else if (typeof callback !== 'function') { + cb = noOp; + opt = options; + } else { + cb = callback; + opt = options; + } + + let assetDetails = {}; + let contentUrl = undefined; + + let report = function (content, error) { + let details = { + assetKey: key, + content: content, + }; + + if (error) { + details.error = assetDetails.error = error; + } else { + assetDetails.error = undefined; + } + + cb(details); + }; + + let onContentNotLoaded = function (details, tries) { + let external; + let urls = []; + + let tr = (tries === undefined) ? 10 : tries; + + if (tr <= 0) { + console.warn('ηMatrix couldn\'t download the asset ' + +assetDetails.title); + return; + } + + if (typeof assetDetails.contentURL === 'string') { + urls = [assetDetails.contentURL]; + } else if (Array.isArray(assetDetails.contentURL)) { + urls = assetDetails.contentURL.slice(0); + } + + while ((contentUrl = urls.shift())) { + external = externalPathRegex.test(contentUrl); + if (external === true && assetDetails.loaded !== true) { + break; + } + if (external === false || assetDetails.hasLocalURL !== true) { + break; + } + } + + if (!contentUrl) { + report('', 'E_NOTFOUND'); + return; + } + + api.fetchText(contentUrl, onContentLoaded, onContentNotLoaded, + tr-1); + }; + + let onContentLoaded = function (details, tries) { + if (isEmptyString(details.content) === true) { + onContentNotLoaded(undefined, tries); + return; + } + + if (externalPathRegex.test(details.url) + && opt.dontCache !== true) { + writeCache(key, { + content: details.content, + url: contentUrl, + }); + } + + assetDetails.loaded = true; + + report(details.content); + }; + + let onCachedContentLoad = function (details) { + if (details.content !== '') { + report(details.content); + return; + } + + let onReady = function (registry) { + assetDetails = registry[key] || {}; + onContentNotLoaded(); + } + + getSourceRegistry(onReady); + }; + + readCache(key, onCachedContentLoad); }; api.put = function (key, content, callback) { - writeCache(key, content, callback); + writeCache(key, content, callback); }; api.metadata = function (callback) { - let onSourceReady = function (registry) { - let source = JSON.parse(JSON.stringify(registry)); - let cache = cacheRegistry; - let sourceEntry; - let cacheEntry; - let now = Date.now(); - let obsoleteAfter; - - for (let key in source) { - sourceEntry = source[key]; - cacheEntry = cache[key]; - - if (cacheEntry) { - sourceEntry.cached = true; - sourceEntry.writeTime = cacheEntry.writeTime; - obsoleteAfter = cacheEntry.writeTime - + sourceEntry.updateAfter * 86400000; - sourceEntry.obsolete = obsoleteAfter < now; - sourceEntry.remoteURL = cacheEntry.remoteURL; - } else { - sourceEntry.writeTime = 0; - obsoleteAfter = 0; - sourceEntry.obsolete = true; - } - } - - callback(source); - } - - let onCacheReady = function () { - getSourceRegistry(onSourceReady); - } - - getCacheRegistry(onCacheReady); + let onSourceReady = function (registry) { + let source = JSON.parse(JSON.stringify(registry)); + let cache = cacheRegistry; + let sourceEntry; + let cacheEntry; + let now = Date.now(); + let obsoleteAfter; + + for (let key in source) { + sourceEntry = source[key]; + cacheEntry = cache[key]; + + if (cacheEntry) { + sourceEntry.cached = true; + sourceEntry.writeTime = cacheEntry.writeTime; + obsoleteAfter = cacheEntry.writeTime + + sourceEntry.updateAfter * 86400000; + sourceEntry.obsolete = obsoleteAfter < now; + sourceEntry.remoteURL = cacheEntry.remoteURL; + } else { + sourceEntry.writeTime = 0; + obsoleteAfter = 0; + sourceEntry.obsolete = true; + } + } + + callback(source); + } + + let onCacheReady = function () { + getSourceRegistry(onSourceReady); + } + + getCacheRegistry(onCacheReady); }; api.purge = function (pattern, exclude, callback) { - markDirtyCache(pattern, exclude, callback); + markDirtyCache(pattern, exclude, callback); }; api.remove = function (pattern, callback) { - cacheRemove(pattern, callback); + cacheRemove(pattern, callback); }; api.rmrf = function () { - cacheRemove(/./); + cacheRemove(/./); }; api.updateStart = function (details) { - let oldDelay = updateDelay; - let newDelay = details.delay || updateDefaultDelay; + let oldDelay = updateDelay; + let newDelay = details.delay || updateDefaultDelay; - updateDelay = Math.min(oldDelay, newDelay); + updateDelay = Math.min(oldDelay, newDelay); - if (updateStatus === 'running') { - if (newDelay < oldDelay) { - clearTimeout(updateTimer); - updateTimer = vAPI.setTimeout(updateNext, updateDelay); - } - return; - } + if (updateStatus === 'running') { + if (newDelay < oldDelay) { + clearTimeout(updateTimer); + updateTimer = vAPI.setTimeout(updateNext, updateDelay); + } + return; + } - updateStart(); + updateStart(); }; api.updateStop = function () { - if (updateTimer) { - clearTimeout(updateTimer); - updateTimer = undefined; - } - if (updateStatus === 'running') { - updateEnd(); - } + if (updateTimer) { + clearTimeout(updateTimer); + updateTimer = undefined; + } + if (updateStatus === 'running') { + updateEnd(); + } }; return api; diff --git a/js/background.js b/js/background.js index aad19e7..07065b7 100644 --- a/js/background.js +++ b/js/background.js @@ -27,61 +27,61 @@ var ηMatrix = (function() { // jshint ignore:line -/******************************************************************************/ - -var oneSecond = 1000; -var oneMinute = 60 * oneSecond; -var oneHour = 60 * oneMinute; -var oneDay = 24 * oneHour; - -/******************************************************************************/ -/******************************************************************************/ - -var _RequestStats = function() { - this.reset(); -}; - -_RequestStats.prototype.reset = function() { - this.all = - this.doc = - this.frame = - this.script = - this.css = - this.image = - this.media = - this.xhr = - this.other = - this.cookie = 0; -}; - -/******************************************************************************/ - -var RequestStats = function() { - this.allowed = new _RequestStats(); - this.blocked = new _RequestStats(); -}; - -RequestStats.prototype.reset = function() { - this.blocked.reset(); - this.allowed.reset(); -}; - -RequestStats.prototype.record = function(type, blocked) { - // Remember: always test against **false** - if ( blocked !== false ) { - this.blocked[type] += 1; - this.blocked.all += 1; - } else { - this.allowed[type] += 1; - this.allowed.all += 1; - } -}; - -var requestStatsFactory = function() { - return new RequestStats(); -}; - -/******************************************************************************* + /******************************************************************************/ + + var oneSecond = 1000; + var oneMinute = 60 * oneSecond; + var oneHour = 60 * oneMinute; + var oneDay = 24 * oneHour; + + /******************************************************************************/ + /******************************************************************************/ + + var _RequestStats = function() { + this.reset(); + }; + + _RequestStats.prototype.reset = function() { + this.all = + this.doc = + this.frame = + this.script = + this.css = + this.image = + this.media = + this.xhr = + this.other = + this.cookie = 0; + }; + + /******************************************************************************/ + + var RequestStats = function() { + this.allowed = new _RequestStats(); + this.blocked = new _RequestStats(); + }; + + RequestStats.prototype.reset = function() { + this.blocked.reset(); + this.allowed.reset(); + }; + + RequestStats.prototype.record = function(type, blocked) { + // Remember: always test against **false** + if ( blocked !== false ) { + this.blocked[type] += 1; + this.blocked.all += 1; + } else { + this.allowed[type] += 1; + this.allowed.all += 1; + } + }; + + var requestStatsFactory = function() { + return new RequestStats(); + }; + + /******************************************************************************* SVG-based icons below were extracted from fontawesome-webfont.svg v4.7. Excerpt of copyright notice at @@ -100,56 +100,56 @@ var requestStatsFactory = function() { Font icons: - glyph-name: "external_link" -*/ + */ -var rawSettingsDefault = { - disableCSPReportInjection: false, - placeholderBackground: + var rawSettingsDefault = { + disableCSPReportInjection: false, + placeholderBackground: [ 'url("data:image/png;base64,', - 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAK', - 'CAAAAACoWZBhAAAABGdBTUEAALGPC/xh', - 'BQAAAAJiS0dEAP+Hj8y/AAAAB3RJTUUH', - '3wwIAAgyL/YaPAAAACJJREFUCFtjfMbO', - 'AAQ/gZiFnQPEBAEmGIMIJgtIL8QEgtoA', - 'In4D/96X1KAAAAAldEVYdGRhdGU6Y3Jl', - 'YXRlADIwMTUtMTItMDhUMDA6MDg6NTAr', - 'MDM6MDAasuuJAAAAJXRFWHRkYXRlOm1v', - 'ZGlmeQAyMDE1LTEyLTA4VDAwOjA4OjUw', - 'KzAzOjAwa+9TNQAAAABJRU5ErkJggg==', + 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAK', + 'CAAAAACoWZBhAAAABGdBTUEAALGPC/xh', + 'BQAAAAJiS0dEAP+Hj8y/AAAAB3RJTUUH', + '3wwIAAgyL/YaPAAAACJJREFUCFtjfMbO', + 'AAQ/gZiFnQPEBAEmGIMIJgtIL8QEgtoA', + 'In4D/96X1KAAAAAldEVYdGRhdGU6Y3Jl', + 'YXRlADIwMTUtMTItMDhUMDA6MDg6NTAr', + 'MDM6MDAasuuJAAAAJXRFWHRkYXRlOm1v', + 'ZGlmeQAyMDE1LTEyLTA4VDAwOjA4OjUw', + 'KzAzOjAwa+9TNQAAAABJRU5ErkJggg==', '") ', 'repeat scroll #fff' ].join(''), - placeholderBorder: '1px solid rgba(0, 0, 0, 0.1)', - imagePlaceholder: true, - imagePlaceholderBackground: 'default', - imagePlaceholderBorder: 'default', - framePlaceholder: true, - framePlaceholderDocument: + placeholderBorder: '1px solid rgba(0, 0, 0, 0.1)', + imagePlaceholder: true, + imagePlaceholderBackground: 'default', + imagePlaceholderBorder: 'default', + framePlaceholder: true, + framePlaceholderDocument: [ '<html><head>', '<meta charset="utf-8">', '<style>', 'body { ', - 'background: {{bg}};', - 'color: gray;', - 'font: 12px sans-serif;', - 'margin: 0;', - 'overflow: hidden;', - 'padding: 2px;', - 'white-space: nowrap;', + 'background: {{bg}};', + 'color: gray;', + 'font: 12px sans-serif;', + 'margin: 0;', + 'overflow: hidden;', + 'padding: 2px;', + 'white-space: nowrap;', '}', 'a { ', - 'color: inherit;', - 'padding: 0 3px;', - 'text-decoration: none;', + 'color: inherit;', + 'padding: 0 3px;', + 'text-decoration: none;', '}', 'svg {', - 'display: inline-block;', - 'fill: gray;', - 'height: 12px;', - 'vertical-align: bottom;', - 'width: 12px;', + 'display: inline-block;', + 'fill: gray;', + 'height: 12px;', + 'vertical-align: bottom;', + 'width: 12px;', '}', '</style></head><body>', '<span><a href="{{url}}" title="{{url}}" target="_blank">', @@ -157,91 +157,90 @@ var rawSettingsDefault = { '</a>{{url}}</span>', '</body></html>' ].join(''), - framePlaceholderBackground: 'default', -}; - -/******************************************************************************/ - -return { - onBeforeStartQueue: [], - - userSettings: { - alwaysDetachLogger: false, - autoUpdate: false, - clearBrowserCache: true, - clearBrowserCacheAfter: 60, - cloudStorageEnabled: false, - collapseBlacklisted: true, - collapseBlocked: false, - colorBlindFriendly: false, - deleteCookies: false, - deleteUnusedSessionCookies: false, - deleteUnusedSessionCookiesAfter: 60, - deleteLocalStorage: false, - displayTextSize: '14px', - externalHostsFiles: '', - iconBadgeEnabled: false, - maxLoggedRequests: 1000, - popupCollapseAllDomains: false, - popupCollapseBlacklistedDomains: false, - popupScopeLevel: 'domain', - processHyperlinkAuditing: true, - processReferer: false - }, - - rawSettingsDefault: rawSettingsDefault, - rawSettings: Object.assign({}, rawSettingsDefault), - rawSettingsWriteTime: 0, - - clearBrowserCacheCycle: 0, - cspNoInlineScript: "script-src 'unsafe-eval' blob: *", - cspNoInlineStyle: "style-src blob: *", - cspNoWorker: undefined, - updateAssetsEvery: 11 * oneDay + 1 * oneHour + 1 * oneMinute + 1 * oneSecond, - firstUpdateAfter: 11 * oneMinute, - nextUpdateAfter: 11 * oneHour, - assetsBootstrapLocation: 'assets/assets.json', - pslAssetKey: 'public_suffix_list.dat', - - // list of live hosts files - liveHostsFiles: { - }, - - // urls stats are kept on the back burner while waiting to be reactivated - // in a tab or another. - pageStores: {}, - pageStoresToken: 0, - pageStoreCemetery: {}, - - // page url => permission scope - tMatrix: null, - pMatrix: null, - - ubiquitousBlacklist: null, - - // various stats - requestStatsFactory: requestStatsFactory, - requestStats: requestStatsFactory(), - cookieRemovedCounter: 0, - localStorageRemovedCounter: 0, - cookieHeaderFoiledCounter: 0, - refererHeaderFoiledCounter: 0, - hyperlinkAuditingFoiledCounter: 0, - browserCacheClearedCounter: 0, - storageUsed: 0, - - // record what the browser is doing behind the scene - behindTheSceneScope: 'behind-the-scene', - - noopFunc: function(){}, - - // so that I don't have to care for last comma - dummy: 0 -}; - -/******************************************************************************/ + framePlaceholderBackground: 'default', + }; + + /******************************************************************************/ + + return { + onBeforeStartQueue: [], + + userSettings: { + alwaysDetachLogger: false, + autoUpdate: false, + clearBrowserCache: true, + clearBrowserCacheAfter: 60, + cloudStorageEnabled: false, + collapseBlacklisted: true, + collapseBlocked: false, + colorBlindFriendly: false, + deleteCookies: false, + deleteUnusedSessionCookies: false, + deleteUnusedSessionCookiesAfter: 60, + deleteLocalStorage: false, + displayTextSize: '14px', + externalHostsFiles: '', + iconBadgeEnabled: false, + maxLoggedRequests: 1000, + popupCollapseAllDomains: false, + popupCollapseBlacklistedDomains: false, + popupScopeLevel: 'domain', + processHyperlinkAuditing: true, + processReferer: false + }, + + rawSettingsDefault: rawSettingsDefault, + rawSettings: Object.assign({}, rawSettingsDefault), + rawSettingsWriteTime: 0, + + clearBrowserCacheCycle: 0, + cspNoInlineScript: "script-src 'unsafe-eval' blob: *", + cspNoInlineStyle: "style-src blob: *", + cspNoWorker: undefined, + updateAssetsEvery: 11 * oneDay + 1 * oneHour + 1 * oneMinute + 1 * oneSecond, + firstUpdateAfter: 11 * oneMinute, + nextUpdateAfter: 11 * oneHour, + assetsBootstrapLocation: 'assets/assets.json', + pslAssetKey: 'public_suffix_list.dat', + + // list of live hosts files + liveHostsFiles: { + }, + + // urls stats are kept on the back burner while waiting to be reactivated + // in a tab or another. + pageStores: {}, + pageStoresToken: 0, + pageStoreCemetery: {}, + + // page url => permission scope + tMatrix: null, + pMatrix: null, + + ubiquitousBlacklist: null, + + // various stats + requestStatsFactory: requestStatsFactory, + requestStats: requestStatsFactory(), + cookieRemovedCounter: 0, + localStorageRemovedCounter: 0, + cookieHeaderFoiledCounter: 0, + refererHeaderFoiledCounter: 0, + hyperlinkAuditingFoiledCounter: 0, + browserCacheClearedCounter: 0, + storageUsed: 0, + + // record what the browser is doing behind the scene + behindTheSceneScope: 'behind-the-scene', + + noopFunc: function(){}, + + // so that I don't have to care for last comma + dummy: 0 + }; + + /******************************************************************************/ })(); /******************************************************************************/ - diff --git a/js/browsercache.js b/js/browsercache.js index d8ae19f..1360f92 100644 --- a/js/browsercache.js +++ b/js/browsercache.js @@ -29,37 +29,37 @@ (function() { -/******************************************************************************/ + /******************************************************************************/ -// Browser data jobs + // Browser data jobs -var clearCache = function() { - vAPI.setTimeout(clearCache, 15 * 60 * 1000); + var clearCache = function() { + vAPI.setTimeout(clearCache, 15 * 60 * 1000); - var ηm = ηMatrix; - if ( !ηm.userSettings.clearBrowserCache ) { - return; - } + var ηm = ηMatrix; + if ( !ηm.userSettings.clearBrowserCache ) { + return; + } - ηm.clearBrowserCacheCycle -= 15; - if ( ηm.clearBrowserCacheCycle > 0 ) { - return; - } + ηm.clearBrowserCacheCycle -= 15; + if ( ηm.clearBrowserCacheCycle > 0 ) { + return; + } - vAPI.browser.data.clearCache(); + vAPI.browser.data.clearCache(); - ηm.clearBrowserCacheCycle = ηm.userSettings.clearBrowserCacheAfter; - ηm.browserCacheClearedCounter++; + ηm.clearBrowserCacheCycle = ηm.userSettings.clearBrowserCacheAfter; + ηm.browserCacheClearedCounter++; - // TODO: i18n - ηm.logger.writeOne('', 'info', vAPI.i18n('loggerEntryBrowserCacheCleared')); + // TODO: i18n + ηm.logger.writeOne('', 'info', vAPI.i18n('loggerEntryBrowserCacheCleared')); - //console.debug('clearBrowserCacheCallback()> vAPI.browser.data.clearCache() called'); -}; + //console.debug('clearBrowserCacheCallback()> vAPI.browser.data.clearCache() called'); + }; -vAPI.setTimeout(clearCache, 15 * 60 * 1000); + vAPI.setTimeout(clearCache, 15 * 60 * 1000); -/******************************************************************************/ + /******************************************************************************/ })(); diff --git a/js/cloud-ui.js b/js/cloud-ui.js index 1afd9d1..0f81833 100644 --- a/js/cloud-ui.js +++ b/js/cloud-ui.js @@ -29,187 +29,187 @@ (function() { -/******************************************************************************/ - -self.cloud = { - options: {}, - datakey: '', - data: undefined, - onPush: null, - onPull: null -}; - -/******************************************************************************/ - -var widget = uDom.nodeFromId('cloudWidget'); -if ( widget === null ) { - return; -} + /******************************************************************************/ + + self.cloud = { + options: {}, + datakey: '', + data: undefined, + onPush: null, + onPull: null + }; -self.cloud.datakey = widget.getAttribute('data-cloud-entry') || ''; -if ( self.cloud.datakey === '' ) { - return; -} + /******************************************************************************/ -/******************************************************************************/ + var widget = uDom.nodeFromId('cloudWidget'); + if ( widget === null ) { + return; + } -var onCloudDataReceived = function(entry) { - if ( typeof entry !== 'object' || entry === null ) { + self.cloud.datakey = widget.getAttribute('data-cloud-entry') || ''; + if ( self.cloud.datakey === '' ) { return; } - self.cloud.data = entry.data; + /******************************************************************************/ - uDom.nodeFromId('cloudPull').removeAttribute('disabled'); - uDom.nodeFromId('cloudPullAndMerge').removeAttribute('disabled'); + var onCloudDataReceived = function(entry) { + if ( typeof entry !== 'object' || entry === null ) { + return; + } - var timeOptions = { - weekday: 'short', - year: 'numeric', - month: 'short', - day: 'numeric', - hour: 'numeric', - minute: 'numeric', - second: 'numeric', - timeZoneName: 'short' + self.cloud.data = entry.data; + + uDom.nodeFromId('cloudPull').removeAttribute('disabled'); + uDom.nodeFromId('cloudPullAndMerge').removeAttribute('disabled'); + + var timeOptions = { + weekday: 'short', + year: 'numeric', + month: 'short', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', + timeZoneName: 'short' + }; + + var time = new Date(entry.tstamp); + widget.querySelector('span').textContent = + entry.source + '\n' + + time.toLocaleString('fullwide', timeOptions); }; - var time = new Date(entry.tstamp); - widget.querySelector('span').textContent = - entry.source + '\n' + - time.toLocaleString('fullwide', timeOptions); -}; - -/******************************************************************************/ - -var fetchCloudData = function() { - vAPI.messaging.send( - 'cloud-ui.js', - { - what: 'cloudPull', - datakey: self.cloud.datakey - }, - onCloudDataReceived - ); -}; - -/******************************************************************************/ - -var pushData = function() { - if ( typeof self.cloud.onPush !== 'function' ) { - return; - } - vAPI.messaging.send( - 'cloud-ui.js', - { - what: 'cloudPush', - datakey: self.cloud.datakey, - data: self.cloud.onPush() - }, - fetchCloudData - ); -}; + /******************************************************************************/ + + var fetchCloudData = function() { + vAPI.messaging.send( + 'cloud-ui.js', + { + what: 'cloudPull', + datakey: self.cloud.datakey + }, + onCloudDataReceived + ); + }; -/******************************************************************************/ + /******************************************************************************/ -var pullData = function(ev) { - if ( typeof self.cloud.onPull === 'function' ) { - self.cloud.onPull(self.cloud.data, ev.shiftKey); - } -}; + var pushData = function() { + if ( typeof self.cloud.onPush !== 'function' ) { + return; + } + vAPI.messaging.send( + 'cloud-ui.js', + { + what: 'cloudPush', + datakey: self.cloud.datakey, + data: self.cloud.onPush() + }, + fetchCloudData + ); + }; -/******************************************************************************/ + /******************************************************************************/ -var pullAndMergeData = function() { - if ( typeof self.cloud.onPull === 'function' ) { - self.cloud.onPull(self.cloud.data, true); - } -}; + var pullData = function(ev) { + if ( typeof self.cloud.onPull === 'function' ) { + self.cloud.onPull(self.cloud.data, ev.shiftKey); + } + }; -/******************************************************************************/ + /******************************************************************************/ -var openOptions = function() { - var input = uDom.nodeFromId('cloudDeviceName'); - input.value = self.cloud.options.deviceName; - input.setAttribute('placeholder', self.cloud.options.defaultDeviceName); - uDom.nodeFromId('cloudOptions').classList.add('show'); -}; + var pullAndMergeData = function() { + if ( typeof self.cloud.onPull === 'function' ) { + self.cloud.onPull(self.cloud.data, true); + } + }; -/******************************************************************************/ + /******************************************************************************/ -var closeOptions = function(ev) { - var root = uDom.nodeFromId('cloudOptions'); - if ( ev.target !== root ) { - return; - } - root.classList.remove('show'); -}; + var openOptions = function() { + var input = uDom.nodeFromId('cloudDeviceName'); + input.value = self.cloud.options.deviceName; + input.setAttribute('placeholder', self.cloud.options.defaultDeviceName); + uDom.nodeFromId('cloudOptions').classList.add('show'); + }; -/******************************************************************************/ + /******************************************************************************/ -var submitOptions = function() { - var onOptions = function(options) { - if ( typeof options !== 'object' || options === null ) { + var closeOptions = function(ev) { + var root = uDom.nodeFromId('cloudOptions'); + if ( ev.target !== root ) { return; } - self.cloud.options = options; + root.classList.remove('show'); }; - vAPI.messaging.send('cloud-ui.js', { - what: 'cloudSetOptions', - options: { - deviceName: uDom.nodeFromId('cloudDeviceName').value - } - }, onOptions); - uDom.nodeFromId('cloudOptions').classList.remove('show'); -}; - -/******************************************************************************/ + /******************************************************************************/ + + var submitOptions = function() { + var onOptions = function(options) { + if ( typeof options !== 'object' || options === null ) { + return; + } + self.cloud.options = options; + }; + + vAPI.messaging.send('cloud-ui.js', { + what: 'cloudSetOptions', + options: { + deviceName: uDom.nodeFromId('cloudDeviceName').value + } + }, onOptions); + uDom.nodeFromId('cloudOptions').classList.remove('show'); + }; -var onInitialize = function(options) { - if ( typeof options !== 'object' || options === null ) { - return; - } + /******************************************************************************/ - if ( !options.enabled ) { - return; - } - self.cloud.options = options; - - var xhr = new XMLHttpRequest(); - xhr.open('GET', 'cloud-ui.html', true); - xhr.overrideMimeType('text/html;charset=utf-8'); - xhr.responseType = 'text'; - xhr.onload = function() { - this.onload = null; - var parser = new DOMParser(), - parsed = parser.parseFromString(this.responseText, 'text/html'), - fromParent = parsed.body; - while ( fromParent.firstElementChild !== null ) { - widget.appendChild( - document.adoptNode(fromParent.firstElementChild) - ); + var onInitialize = function(options) { + if ( typeof options !== 'object' || options === null ) { + return; } - vAPI.i18n.render(widget); - widget.classList.remove('hide'); - - uDom('#cloudPush').on('click', pushData); - uDom('#cloudPull').on('click', pullData); - uDom('#cloudPullAndMerge').on('click', pullAndMergeData); - uDom('#cloudCog').on('click', openOptions); - uDom('#cloudOptions').on('click', closeOptions); - uDom('#cloudOptionsSubmit').on('click', submitOptions); + if ( !options.enabled ) { + return; + } + self.cloud.options = options; - fetchCloudData(); + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'cloud-ui.html', true); + xhr.overrideMimeType('text/html;charset=utf-8'); + xhr.responseType = 'text'; + xhr.onload = function() { + this.onload = null; + var parser = new DOMParser(), + parsed = parser.parseFromString(this.responseText, 'text/html'), + fromParent = parsed.body; + while ( fromParent.firstElementChild !== null ) { + widget.appendChild( + document.adoptNode(fromParent.firstElementChild) + ); + } + + vAPI.i18n.render(widget); + widget.classList.remove('hide'); + + uDom('#cloudPush').on('click', pushData); + uDom('#cloudPull').on('click', pullData); + uDom('#cloudPullAndMerge').on('click', pullAndMergeData); + uDom('#cloudCog').on('click', openOptions); + uDom('#cloudOptions').on('click', closeOptions); + uDom('#cloudOptionsSubmit').on('click', submitOptions); + + fetchCloudData(); + }; + xhr.send(); }; - xhr.send(); -}; -vAPI.messaging.send('cloud-ui.js', { what: 'cloudGetOptions' }, onInitialize); + vAPI.messaging.send('cloud-ui.js', { what: 'cloudGetOptions' }, onInitialize); -/******************************************************************************/ + /******************************************************************************/ -// https://www.youtube.com/watch?v=aQFp67VoiDA + // https://www.youtube.com/watch?v=aQFp67VoiDA })(); diff --git a/js/contentscript-start.js b/js/contentscript-start.js index 80799ef..3096023 100644 --- a/js/contentscript-start.js +++ b/js/contentscript-start.js @@ -39,7 +39,7 @@ var handler = function(ev) { if ( ev.isTrusted !== true || - ev.originalPolicy.includes('report-uri about:blank') === false + ev.originalPolicy.includes('report-uri about:blank') === false ) { return false; } @@ -48,7 +48,7 @@ // 'effectiveDirective' property. if ( ev.effectiveDirective.startsWith('worker-src') === false && - ev.effectiveDirective.startsWith('frame-src') === false + ev.effectiveDirective.startsWith('frame-src') === false ) { return false; } diff --git a/js/contentscript.js b/js/contentscript.js index a4595cb..650d1be 100644 --- a/js/contentscript.js +++ b/js/contentscript.js @@ -32,511 +32,511 @@ (function() { -/******************************************************************************/ + /******************************************************************************/ -// https://github.com/chrisaljoudi/uBlock/issues/464 -// https://github.com/gorhill/uMatrix/issues/621 -if ( - document instanceof HTMLDocument === false && - document instanceof XMLDocument === false -) { - return; -} - -// This can also happen (for example if script injected into a `data:` URI doc) -if ( !window.location ) { - return; -} - -// This can happen -if ( typeof vAPI !== 'object' ) { - //console.debug('contentscript.js > vAPI not found'); - return; -} - -// https://github.com/chrisaljoudi/uBlock/issues/456 -// Already injected? -if ( vAPI.contentscriptEndInjected ) { - //console.debug('contentscript.js > content script already injected'); - return; -} -vAPI.contentscriptEndInjected = true; + // https://github.com/chrisaljoudi/uBlock/issues/464 + // https://github.com/gorhill/uMatrix/issues/621 + if ( + document instanceof HTMLDocument === false && + document instanceof XMLDocument === false + ) { + return; + } -/******************************************************************************/ -/******************************************************************************/ + // This can also happen (for example if script injected into a `data:` URI doc) + if ( !window.location ) { + return; + } -// Executed only once. + // This can happen + if ( typeof vAPI !== 'object' ) { + //console.debug('contentscript.js > vAPI not found'); + return; + } -(function() { - var localStorageHandler = function(mustRemove) { - if ( mustRemove ) { - window.localStorage.clear(); - window.sessionStorage.clear(); - } - }; - - // Check with extension whether local storage must be emptied - // rhill 2014-03-28: we need an exception handler in case 3rd-party access - // to site data is disabled. - // https://github.com/gorhill/httpswitchboard/issues/215 - try { - var hasLocalStorage = - window.localStorage && window.localStorage.length !== 0; - var hasSessionStorage = - window.sessionStorage && window.sessionStorage.length !== 0; - if ( hasLocalStorage || hasSessionStorage ) { - vAPI.messaging.send('contentscript.js', { - what: 'contentScriptHasLocalStorage', - originURL: window.location.origin - }, localStorageHandler); - } + // https://github.com/chrisaljoudi/uBlock/issues/456 + // Already injected? + if ( vAPI.contentscriptEndInjected ) { + //console.debug('contentscript.js > content script already injected'); + return; + } + vAPI.contentscriptEndInjected = true; + + /******************************************************************************/ + /******************************************************************************/ + + // Executed only once. + + (function() { + var localStorageHandler = function(mustRemove) { + if ( mustRemove ) { + window.localStorage.clear(); + window.sessionStorage.clear(); + } + }; + + // Check with extension whether local storage must be emptied + // rhill 2014-03-28: we need an exception handler in case 3rd-party access + // to site data is disabled. + // https://github.com/gorhill/httpswitchboard/issues/215 + try { + var hasLocalStorage = + window.localStorage && window.localStorage.length !== 0; + var hasSessionStorage = + window.sessionStorage && window.sessionStorage.length !== 0; + if ( hasLocalStorage || hasSessionStorage ) { + vAPI.messaging.send('contentscript.js', { + what: 'contentScriptHasLocalStorage', + originURL: window.location.origin + }, localStorageHandler); + } - // TODO: indexedDB - //if ( window.indexedDB && !!window.indexedDB.webkitGetDatabaseNames ) { + // TODO: indexedDB + //if ( window.indexedDB && !!window.indexedDB.webkitGetDatabaseNames ) { // var db = window.indexedDB.webkitGetDatabaseNames().onsuccess = function(sender) { // console.debug('webkitGetDatabaseNames(): result=%o', sender.target.result); // }; - //} + //} - // TODO: Web SQL - // if ( window.openDatabase ) { + // TODO: Web SQL + // if ( window.openDatabase ) { // Sad: // "There is no way to enumerate or delete the databases available for an origin from this API." // Ref.: http://www.w3.org/TR/webdatabase/#databases - // } - } - catch (e) { - } -})(); - -/******************************************************************************/ -/******************************************************************************/ - -// https://github.com/gorhill/uMatrix/issues/45 - -var collapser = (function() { - var resquestIdGenerator = 1, - processTimer, - toProcess = [], - toFilter = [], - toCollapse = new Map(), - cachedBlockedMap, - cachedBlockedMapHash, - cachedBlockedMapTimer, - reURLPlaceholder = /\{\{url\}\}/g; - var src1stProps = { - 'embed': 'src', - 'iframe': 'src', - 'img': 'src', - 'object': 'data' - }; - var src2ndProps = { - 'img': 'srcset' - }; - var tagToTypeMap = { - embed: 'media', - iframe: 'frame', - img: 'image', - object: 'media' - }; - var cachedBlockedSetClear = function() { - cachedBlockedMap = - cachedBlockedMapHash = - cachedBlockedMapTimer = undefined; - }; - - // https://github.com/chrisaljoudi/uBlock/issues/174 - // Do not remove fragment from src URL - var onProcessed = function(response) { - if ( !response ) { // This happens if uBO is disabled or restarted. - toCollapse.clear(); - return; + // } } + catch (e) { + } + })(); + + /******************************************************************************/ + /******************************************************************************/ + + // https://github.com/gorhill/uMatrix/issues/45 + + var collapser = (function() { + var resquestIdGenerator = 1, + processTimer, + toProcess = [], + toFilter = [], + toCollapse = new Map(), + cachedBlockedMap, + cachedBlockedMapHash, + cachedBlockedMapTimer, + reURLPlaceholder = /\{\{url\}\}/g; + var src1stProps = { + 'embed': 'src', + 'iframe': 'src', + 'img': 'src', + 'object': 'data' + }; + var src2ndProps = { + 'img': 'srcset' + }; + var tagToTypeMap = { + embed: 'media', + iframe: 'frame', + img: 'image', + object: 'media' + }; + var cachedBlockedSetClear = function() { + cachedBlockedMap = + cachedBlockedMapHash = + cachedBlockedMapTimer = undefined; + }; - var targets = toCollapse.get(response.id); - if ( targets === undefined ) { return; } - toCollapse.delete(response.id); - if ( cachedBlockedMapHash !== response.hash ) { - cachedBlockedMap = new Map(response.blockedResources); - cachedBlockedMapHash = response.hash; - if ( cachedBlockedMapTimer !== undefined ) { - clearTimeout(cachedBlockedMapTimer); + // https://github.com/chrisaljoudi/uBlock/issues/174 + // Do not remove fragment from src URL + var onProcessed = function(response) { + if ( !response ) { // This happens if uBO is disabled or restarted. + toCollapse.clear(); + return; + } + + var targets = toCollapse.get(response.id); + if ( targets === undefined ) { return; } + toCollapse.delete(response.id); + if ( cachedBlockedMapHash !== response.hash ) { + cachedBlockedMap = new Map(response.blockedResources); + cachedBlockedMapHash = response.hash; + if ( cachedBlockedMapTimer !== undefined ) { + clearTimeout(cachedBlockedMapTimer); + } + cachedBlockedMapTimer = vAPI.setTimeout(cachedBlockedSetClear, 30000); + } + if ( cachedBlockedMap === undefined || cachedBlockedMap.size === 0 ) { + return; } - cachedBlockedMapTimer = vAPI.setTimeout(cachedBlockedSetClear, 30000); - } - if ( cachedBlockedMap === undefined || cachedBlockedMap.size === 0 ) { - return; - } - var placeholders = response.placeholders, - tag, prop, src, collapsed, docurl, replaced; + var placeholders = response.placeholders, + tag, prop, src, collapsed, docurl, replaced; - for ( var target of targets ) { - tag = target.localName; - prop = src1stProps[tag]; - if ( prop === undefined ) { continue; } - src = target[prop]; - if ( typeof src !== 'string' || src.length === 0 ) { - prop = src2ndProps[tag]; + for ( var target of targets ) { + tag = target.localName; + prop = src1stProps[tag]; if ( prop === undefined ) { continue; } src = target[prop]; - if ( typeof src !== 'string' || src.length === 0 ) { continue; } - } - collapsed = cachedBlockedMap.get(tagToTypeMap[tag] + ' ' + src); - if ( collapsed === undefined ) { continue; } - if ( collapsed ) { - target.style.setProperty('display', 'none', 'important'); - target.hidden = true; - continue; - } - switch ( tag ) { - case 'iframe': - if ( placeholders.frame !== true ) { break; } - docurl = - 'data:text/html,' + - encodeURIComponent( - placeholders.frameDocument.replace( - reURLPlaceholder, - src - ) - ); - replaced = false; - // Using contentWindow.location prevent tainting browser - // history -- i.e. breaking back button (seen on Chromium). - if ( target.contentWindow ) { - try { - target.contentWindow.location.replace(docurl); - replaced = true; - } catch(ex) { + if ( typeof src !== 'string' || src.length === 0 ) { + prop = src2ndProps[tag]; + if ( prop === undefined ) { continue; } + src = target[prop]; + if ( typeof src !== 'string' || src.length === 0 ) { continue; } + } + collapsed = cachedBlockedMap.get(tagToTypeMap[tag] + ' ' + src); + if ( collapsed === undefined ) { continue; } + if ( collapsed ) { + target.style.setProperty('display', 'none', 'important'); + target.hidden = true; + continue; + } + switch ( tag ) { + case 'iframe': + if ( placeholders.frame !== true ) { break; } + docurl = + 'data:text/html,' + + encodeURIComponent( + placeholders.frameDocument.replace( + reURLPlaceholder, + src + ) + ); + replaced = false; + // Using contentWindow.location prevent tainting browser + // history -- i.e. breaking back button (seen on Chromium). + if ( target.contentWindow ) { + try { + target.contentWindow.location.replace(docurl); + replaced = true; + } catch(ex) { + } } + if ( !replaced ) { + target.setAttribute('src', docurl); + } + break; + case 'img': + if ( placeholders.image !== true ) { break; } + target.style.setProperty('display', 'inline-block'); + target.style.setProperty('min-width', '20px', 'important'); + target.style.setProperty('min-height', '20px', 'important'); + target.style.setProperty( + 'border', + placeholders.imageBorder, + 'important' + ); + target.style.setProperty( + 'background', + placeholders.imageBackground, + 'important' + ); + break; } - if ( !replaced ) { - target.setAttribute('src', docurl); + } + }; + + var send = function() { + processTimer = undefined; + toCollapse.set(resquestIdGenerator, toProcess); + var msg = { + what: 'lookupBlockedCollapsibles', + id: resquestIdGenerator, + toFilter: toFilter, + hash: cachedBlockedMapHash + }; + vAPI.messaging.send('contentscript.js', msg, onProcessed); + toProcess = []; + toFilter = []; + resquestIdGenerator += 1; + }; + + var process = function(delay) { + if ( toProcess.length === 0 ) { return; } + if ( delay === 0 ) { + if ( processTimer !== undefined ) { + clearTimeout(processTimer); } - break; - case 'img': - if ( placeholders.image !== true ) { break; } - target.style.setProperty('display', 'inline-block'); - target.style.setProperty('min-width', '20px', 'important'); - target.style.setProperty('min-height', '20px', 'important'); - target.style.setProperty( - 'border', - placeholders.imageBorder, - 'important' - ); - target.style.setProperty( - 'background', - placeholders.imageBackground, - 'important' - ); - break; + send(); + } else if ( processTimer === undefined ) { + processTimer = vAPI.setTimeout(send, delay || 47); } - } - }; - - var send = function() { - processTimer = undefined; - toCollapse.set(resquestIdGenerator, toProcess); - var msg = { - what: 'lookupBlockedCollapsibles', - id: resquestIdGenerator, - toFilter: toFilter, - hash: cachedBlockedMapHash }; - vAPI.messaging.send('contentscript.js', msg, onProcessed); - toProcess = []; - toFilter = []; - resquestIdGenerator += 1; - }; - - var process = function(delay) { - if ( toProcess.length === 0 ) { return; } - if ( delay === 0 ) { - if ( processTimer !== undefined ) { - clearTimeout(processTimer); + + var add = function(target) { + toProcess.push(target); + }; + + var addMany = function(targets) { + var i = targets.length; + while ( i-- ) { + toProcess.push(targets[i]); } - send(); - } else if ( processTimer === undefined ) { - processTimer = vAPI.setTimeout(send, delay || 47); - } - }; + }; - var add = function(target) { - toProcess.push(target); - }; + var iframeSourceModified = function(mutations) { + var i = mutations.length; + while ( i-- ) { + addIFrame(mutations[i].target, true); + } + process(); + }; + var iframeSourceObserver; + var iframeSourceObserverOptions = { + attributes: true, + attributeFilter: [ 'src' ] + }; - var addMany = function(targets) { - var i = targets.length; - while ( i-- ) { - toProcess.push(targets[i]); - } - }; + var addIFrame = function(iframe, dontObserve) { + // https://github.com/gorhill/uBlock/issues/162 + // Be prepared to deal with possible change of src attribute. + if ( dontObserve !== true ) { + if ( iframeSourceObserver === undefined ) { + iframeSourceObserver = new MutationObserver(iframeSourceModified); + } + iframeSourceObserver.observe(iframe, iframeSourceObserverOptions); + } + var src = iframe.src; + if ( src === '' || typeof src !== 'string' ) { return; } + if ( src.startsWith('http') === false ) { return; } + toFilter.push({ type: 'frame', url: iframe.src }); + add(iframe); + }; - var iframeSourceModified = function(mutations) { - var i = mutations.length; - while ( i-- ) { - addIFrame(mutations[i].target, true); - } - process(); - }; - var iframeSourceObserver; - var iframeSourceObserverOptions = { - attributes: true, - attributeFilter: [ 'src' ] - }; - - var addIFrame = function(iframe, dontObserve) { - // https://github.com/gorhill/uBlock/issues/162 - // Be prepared to deal with possible change of src attribute. - if ( dontObserve !== true ) { - if ( iframeSourceObserver === undefined ) { - iframeSourceObserver = new MutationObserver(iframeSourceModified); + var addIFrames = function(iframes) { + var i = iframes.length; + while ( i-- ) { + addIFrame(iframes[i]); } - iframeSourceObserver.observe(iframe, iframeSourceObserverOptions); - } - var src = iframe.src; - if ( src === '' || typeof src !== 'string' ) { return; } - if ( src.startsWith('http') === false ) { return; } - toFilter.push({ type: 'frame', url: iframe.src }); - add(iframe); - }; - - var addIFrames = function(iframes) { - var i = iframes.length; - while ( i-- ) { - addIFrame(iframes[i]); - } - }; - - var addNodeList = function(nodeList) { - var node, - i = nodeList.length; - while ( i-- ) { - node = nodeList[i]; - if ( node.nodeType !== 1 ) { continue; } - if ( node.localName === 'iframe' ) { - addIFrame(node); + }; + + var addNodeList = function(nodeList) { + var node, + i = nodeList.length; + while ( i-- ) { + node = nodeList[i]; + if ( node.nodeType !== 1 ) { continue; } + if ( node.localName === 'iframe' ) { + addIFrame(node); + } + if ( node.childElementCount !== 0 ) { + addIFrames(node.querySelectorAll('iframe')); + } + } + }; + + var onResourceFailed = function(ev) { + if ( tagToTypeMap[ev.target.localName] !== undefined ) { + add(ev.target); + process(); } - if ( node.childElementCount !== 0 ) { - addIFrames(node.querySelectorAll('iframe')); + }; + document.addEventListener('error', onResourceFailed, true); + + vAPI.shutdown.add(function() { + document.removeEventListener('error', onResourceFailed, true); + if ( iframeSourceObserver !== undefined ) { + iframeSourceObserver.disconnect(); + iframeSourceObserver = undefined; } - } - }; + if ( processTimer !== undefined ) { + clearTimeout(processTimer); + processTimer = undefined; + } + }); - var onResourceFailed = function(ev) { - if ( tagToTypeMap[ev.target.localName] !== undefined ) { - add(ev.target); - process(); - } - }; - document.addEventListener('error', onResourceFailed, true); - - vAPI.shutdown.add(function() { - document.removeEventListener('error', onResourceFailed, true); - if ( iframeSourceObserver !== undefined ) { - iframeSourceObserver.disconnect(); - iframeSourceObserver = undefined; - } - if ( processTimer !== undefined ) { - clearTimeout(processTimer); - processTimer = undefined; - } - }); - - return { - addMany: addMany, - addIFrames: addIFrames, - addNodeList: addNodeList, - process: process - }; -})(); + return { + addMany: addMany, + addIFrames: addIFrames, + addNodeList: addNodeList, + process: process + }; + })(); -/******************************************************************************/ -/******************************************************************************/ + /******************************************************************************/ + /******************************************************************************/ -// Observe changes in the DOM + // Observe changes in the DOM -// Added node lists will be cumulated here before being processed + // Added node lists will be cumulated here before being processed -(function() { - // This fixes http://acid3.acidtests.org/ - if ( !document.body ) { return; } + (function() { + // This fixes http://acid3.acidtests.org/ + if ( !document.body ) { return; } - var addedNodeLists = []; - var addedNodeListsTimer; + var addedNodeLists = []; + var addedNodeListsTimer; - var treeMutationObservedHandler = function() { - addedNodeListsTimer = undefined; - var i = addedNodeLists.length; - while ( i-- ) { - collapser.addNodeList(addedNodeLists[i]); - } - collapser.process(); - addedNodeLists = []; - }; - - // https://github.com/gorhill/uBlock/issues/205 - // Do not handle added node directly from within mutation observer. - var treeMutationObservedHandlerAsync = function(mutations) { - var iMutation = mutations.length, - nodeList; - while ( iMutation-- ) { - nodeList = mutations[iMutation].addedNodes; - if ( nodeList.length !== 0 ) { - addedNodeLists.push(nodeList); - } - } - if ( addedNodeListsTimer === undefined ) { - addedNodeListsTimer = vAPI.setTimeout(treeMutationObservedHandler, 47); - } - }; - - // https://github.com/gorhill/httpswitchboard/issues/176 - var treeObserver = new MutationObserver(treeMutationObservedHandlerAsync); - treeObserver.observe(document.body, { - childList: true, - subtree: true - }); - - vAPI.shutdown.add(function() { - if ( addedNodeListsTimer !== undefined ) { - clearTimeout(addedNodeListsTimer); + var treeMutationObservedHandler = function() { addedNodeListsTimer = undefined; - } - if ( treeObserver !== null ) { - treeObserver.disconnect(); - treeObserver = undefined; - } - addedNodeLists = []; - }); -})(); - -/******************************************************************************/ -/******************************************************************************/ + var i = addedNodeLists.length; + while ( i-- ) { + collapser.addNodeList(addedNodeLists[i]); + } + collapser.process(); + addedNodeLists = []; + }; -// Executed only once. -// -// https://github.com/gorhill/httpswitchboard/issues/25 -// -// https://github.com/gorhill/httpswitchboard/issues/131 -// Looks for inline javascript also in at least one a[href] element. -// -// https://github.com/gorhill/uMatrix/issues/485 -// Mind "on..." attributes. -// -// https://github.com/gorhill/uMatrix/issues/924 -// Report inline styles. + // https://github.com/gorhill/uBlock/issues/205 + // Do not handle added node directly from within mutation observer. + var treeMutationObservedHandlerAsync = function(mutations) { + var iMutation = mutations.length, + nodeList; + while ( iMutation-- ) { + nodeList = mutations[iMutation].addedNodes; + if ( nodeList.length !== 0 ) { + addedNodeLists.push(nodeList); + } + } + if ( addedNodeListsTimer === undefined ) { + addedNodeListsTimer = vAPI.setTimeout(treeMutationObservedHandler, 47); + } + }; -(function() { - if ( - document.querySelector('script:not([src])') !== null || - document.querySelector('a[href^="javascript:"]') !== null || - document.querySelector('[onabort],[onblur],[oncancel],[oncanplay],[oncanplaythrough],[onchange],[onclick],[onclose],[oncontextmenu],[oncuechange],[ondblclick],[ondrag],[ondragend],[ondragenter],[ondragexit],[ondragleave],[ondragover],[ondragstart],[ondrop],[ondurationchange],[onemptied],[onended],[onerror],[onfocus],[oninput],[oninvalid],[onkeydown],[onkeypress],[onkeyup],[onload],[onloadeddata],[onloadedmetadata],[onloadstart],[onmousedown],[onmouseenter],[onmouseleave],[onmousemove],[onmouseout],[onmouseover],[onmouseup],[onwheel],[onpause],[onplay],[onplaying],[onprogress],[onratechange],[onreset],[onresize],[onscroll],[onseeked],[onseeking],[onselect],[onshow],[onstalled],[onsubmit],[onsuspend],[ontimeupdate],[ontoggle],[onvolumechange],[onwaiting],[onafterprint],[onbeforeprint],[onbeforeunload],[onhashchange],[onlanguagechange],[onmessage],[onoffline],[ononline],[onpagehide],[onpageshow],[onrejectionhandled],[onpopstate],[onstorage],[onunhandledrejection],[onunload],[oncopy],[oncut],[onpaste]') !== null - ) { - vAPI.messaging.send('contentscript.js', { - what: 'securityPolicyViolation', - directive: 'script-src', - documentURI: window.location.href + // https://github.com/gorhill/httpswitchboard/issues/176 + var treeObserver = new MutationObserver(treeMutationObservedHandlerAsync); + treeObserver.observe(document.body, { + childList: true, + subtree: true }); - } - if ( document.querySelector('style,[style]') !== null ) { - vAPI.messaging.send('contentscript.js', { - what: 'securityPolicyViolation', - directive: 'style-src', - documentURI: window.location.href + vAPI.shutdown.add(function() { + if ( addedNodeListsTimer !== undefined ) { + clearTimeout(addedNodeListsTimer); + addedNodeListsTimer = undefined; + } + if ( treeObserver !== null ) { + treeObserver.disconnect(); + treeObserver = undefined; + } + addedNodeLists = []; }); - } - - collapser.addMany(document.querySelectorAll('img')); - collapser.addIFrames(document.querySelectorAll('iframe')); - collapser.process(); -})(); + })(); + + /******************************************************************************/ + /******************************************************************************/ + + // Executed only once. + // + // https://github.com/gorhill/httpswitchboard/issues/25 + // + // https://github.com/gorhill/httpswitchboard/issues/131 + // Looks for inline javascript also in at least one a[href] element. + // + // https://github.com/gorhill/uMatrix/issues/485 + // Mind "on..." attributes. + // + // https://github.com/gorhill/uMatrix/issues/924 + // Report inline styles. + + (function() { + if ( + document.querySelector('script:not([src])') !== null || + document.querySelector('a[href^="javascript:"]') !== null || + document.querySelector('[onabort],[onblur],[oncancel],[oncanplay],[oncanplaythrough],[onchange],[onclick],[onclose],[oncontextmenu],[oncuechange],[ondblclick],[ondrag],[ondragend],[ondragenter],[ondragexit],[ondragleave],[ondragover],[ondragstart],[ondrop],[ondurationchange],[onemptied],[onended],[onerror],[onfocus],[oninput],[oninvalid],[onkeydown],[onkeypress],[onkeyup],[onload],[onloadeddata],[onloadedmetadata],[onloadstart],[onmousedown],[onmouseenter],[onmouseleave],[onmousemove],[onmouseout],[onmouseover],[onmouseup],[onwheel],[onpause],[onplay],[onplaying],[onprogress],[onratechange],[onreset],[onresize],[onscroll],[onseeked],[onseeking],[onselect],[onshow],[onstalled],[onsubmit],[onsuspend],[ontimeupdate],[ontoggle],[onvolumechange],[onwaiting],[onafterprint],[onbeforeprint],[onbeforeunload],[onhashchange],[onlanguagechange],[onmessage],[onoffline],[ononline],[onpagehide],[onpageshow],[onrejectionhandled],[onpopstate],[onstorage],[onunhandledrejection],[onunload],[oncopy],[oncut],[onpaste]') !== null + ) { + vAPI.messaging.send('contentscript.js', { + what: 'securityPolicyViolation', + directive: 'script-src', + documentURI: window.location.href + }); + } -/******************************************************************************/ -/******************************************************************************/ + if ( document.querySelector('style,[style]') !== null ) { + vAPI.messaging.send('contentscript.js', { + what: 'securityPolicyViolation', + directive: 'style-src', + documentURI: window.location.href + }); + } -// Executed only once. + collapser.addMany(document.querySelectorAll('img')); + collapser.addIFrames(document.querySelectorAll('iframe')); + collapser.process(); + })(); + + /******************************************************************************/ + /******************************************************************************/ + + // Executed only once. + + // https://github.com/gorhill/uMatrix/issues/232 + // Force `display` property, Firefox is still affected by the issue. + + (function() { + var noscripts = document.querySelectorAll('noscript'); + if ( noscripts.length === 0 ) { return; } + + var redirectTimer, + reMetaContent = /^\s*(\d+)\s*;\s*url=(['"]?)([^'"]+)\2/i, + reSafeURL = /^https?:\/\//; + + var autoRefresh = function(root) { + var meta = root.querySelector('meta[http-equiv="refresh"][content]'); + if ( meta === null ) { return; } + var match = reMetaContent.exec(meta.getAttribute('content')); + if ( match === null || match[3].trim() === '' ) { return; } + var url = new URL(match[3], document.baseURI); + if ( reSafeURL.test(url.href) === false ) { return; } + redirectTimer = setTimeout( + function() { + location.assign(url.href); + }, + parseInt(match[1], 10) * 1000 + 1 + ); + meta.parentNode.removeChild(meta); + }; -// https://github.com/gorhill/uMatrix/issues/232 -// Force `display` property, Firefox is still affected by the issue. + var morphNoscript = function(from) { + if ( /^application\/(?:xhtml\+)?xml/.test(document.contentType) ) { + var to = document.createElement('span'); + while ( from.firstChild !== null ) { + to.appendChild(from.firstChild); + } + return to; + } + var parser = new DOMParser(); + var doc = parser.parseFromString( + '<span>' + from.textContent + '</span>', + 'text/html' + ); + return document.adoptNode(doc.querySelector('span')); + }; -(function() { - var noscripts = document.querySelectorAll('noscript'); - if ( noscripts.length === 0 ) { return; } - - var redirectTimer, - reMetaContent = /^\s*(\d+)\s*;\s*url=(['"]?)([^'"]+)\2/i, - reSafeURL = /^https?:\/\//; - - var autoRefresh = function(root) { - var meta = root.querySelector('meta[http-equiv="refresh"][content]'); - if ( meta === null ) { return; } - var match = reMetaContent.exec(meta.getAttribute('content')); - if ( match === null || match[3].trim() === '' ) { return; } - var url = new URL(match[3], document.baseURI); - if ( reSafeURL.test(url.href) === false ) { return; } - redirectTimer = setTimeout( - function() { - location.assign(url.href); - }, - parseInt(match[1], 10) * 1000 + 1 - ); - meta.parentNode.removeChild(meta); - }; - - var morphNoscript = function(from) { - if ( /^application\/(?:xhtml\+)?xml/.test(document.contentType) ) { - var to = document.createElement('span'); - while ( from.firstChild !== null ) { - to.appendChild(from.firstChild); + var renderNoscriptTags = function(response) { + if ( response !== true ) { return; } + var parent, span; + for ( var noscript of noscripts ) { + parent = noscript.parentNode; + if ( parent === null ) { continue; } + span = morphNoscript(noscript); + span.style.setProperty('display', 'inline', 'important'); + if ( redirectTimer === undefined ) { + autoRefresh(span); + } + parent.replaceChild(span, noscript); } - return to; - } - var parser = new DOMParser(); - var doc = parser.parseFromString( - '<span>' + from.textContent + '</span>', - 'text/html' + }; + + vAPI.messaging.send( + 'contentscript.js', + { what: 'mustRenderNoscriptTags?' }, + renderNoscriptTags ); - return document.adoptNode(doc.querySelector('span')); - }; - - var renderNoscriptTags = function(response) { - if ( response !== true ) { return; } - var parent, span; - for ( var noscript of noscripts ) { - parent = noscript.parentNode; - if ( parent === null ) { continue; } - span = morphNoscript(noscript); - span.style.setProperty('display', 'inline', 'important'); - if ( redirectTimer === undefined ) { - autoRefresh(span); - } - parent.replaceChild(span, noscript); - } - }; + })(); + + /******************************************************************************/ + /******************************************************************************/ vAPI.messaging.send( 'contentscript.js', - { what: 'mustRenderNoscriptTags?' }, - renderNoscriptTags - ); -})(); - -/******************************************************************************/ -/******************************************************************************/ - -vAPI.messaging.send( - 'contentscript.js', - { what: 'shutdown?' }, - function(response) { - if ( response === true ) { - vAPI.shutdown.exec(); + { what: 'shutdown?' }, + function(response) { + if ( response === true ) { + vAPI.shutdown.exec(); + } } - } -); + ); -/******************************************************************************/ -/******************************************************************************/ + /******************************************************************************/ + /******************************************************************************/ })(); diff --git a/js/cookies.js b/js/cookies.js index 4aa76c0..ee271ca 100644 --- a/js/cookies.js +++ b/js/cookies.js @@ -37,517 +37,516 @@ ηMatrix.cookieHunter = (function() { -/******************************************************************************/ + /******************************************************************************/ -var ηm = ηMatrix; + var ηm = ηMatrix; -var recordPageCookiesQueue = new Map(); -var removePageCookiesQueue = new Map(); -var removeCookieQueue = new Set(); -var cookieDict = new Map(); -var cookieEntryJunkyard = []; -var processRemoveQueuePeriod = 2 * 60 * 1000; -var processCleanPeriod = 10 * 60 * 1000; -var processPageRecordQueueTimer = null; -var processPageRemoveQueueTimer = null; + var recordPageCookiesQueue = new Map(); + var removePageCookiesQueue = new Map(); + var removeCookieQueue = new Set(); + var cookieDict = new Map(); + var cookieEntryJunkyard = []; + var processRemoveQueuePeriod = 2 * 60 * 1000; + var processCleanPeriod = 10 * 60 * 1000; + var processPageRecordQueueTimer = null; + var processPageRemoveQueueTimer = null; -/******************************************************************************/ + /******************************************************************************/ -var CookieEntry = function(cookie) { - this.usedOn = new Set(); - this.init(cookie); -}; - -CookieEntry.prototype.init = function(cookie) { - this.secure = cookie.secure; - this.session = cookie.session; - this.anySubdomain = cookie.domain.charAt(0) === '.'; - this.hostname = this.anySubdomain ? cookie.domain.slice(1) : cookie.domain; - this.domain = ηm.URI.domainFromHostname(this.hostname) || this.hostname; - this.path = cookie.path; - this.name = cookie.name; - this.value = cookie.value; - this.tstamp = Date.now(); - this.usedOn.clear(); - return this; -}; - -// Release anything which may consume too much memory - -CookieEntry.prototype.dispose = function() { - this.hostname = ''; - this.domain = ''; - this.path = ''; - this.name = ''; - this.value = ''; - this.usedOn.clear(); - return this; -}; + var CookieEntry = function(cookie) { + this.usedOn = new Set(); + this.init(cookie); + }; -/******************************************************************************/ + CookieEntry.prototype.init = function(cookie) { + this.secure = cookie.secure; + this.session = cookie.session; + this.anySubdomain = cookie.domain.charAt(0) === '.'; + this.hostname = this.anySubdomain ? cookie.domain.slice(1) : cookie.domain; + this.domain = ηm.URI.domainFromHostname(this.hostname) || this.hostname; + this.path = cookie.path; + this.name = cookie.name; + this.value = cookie.value; + this.tstamp = Date.now(); + this.usedOn.clear(); + return this; + }; -var addCookieToDict = function(cookie) { - var cookieKey = cookieKeyFromCookie(cookie), - cookieEntry = cookieDict.get(cookieKey); - if ( cookieEntry === undefined ) { - cookieEntry = cookieEntryJunkyard.pop(); - if ( cookieEntry ) { - cookieEntry.init(cookie); - } else { - cookieEntry = new CookieEntry(cookie); + // Release anything which may consume too much memory + + CookieEntry.prototype.dispose = function() { + this.hostname = ''; + this.domain = ''; + this.path = ''; + this.name = ''; + this.value = ''; + this.usedOn.clear(); + return this; + }; + + /******************************************************************************/ + + var addCookieToDict = function(cookie) { + var cookieKey = cookieKeyFromCookie(cookie), + cookieEntry = cookieDict.get(cookieKey); + if ( cookieEntry === undefined ) { + cookieEntry = cookieEntryJunkyard.pop(); + if ( cookieEntry ) { + cookieEntry.init(cookie); + } else { + cookieEntry = new CookieEntry(cookie); + } + cookieDict.set(cookieKey, cookieEntry); } - cookieDict.set(cookieKey, cookieEntry); - } - return cookieEntry; -}; + return cookieEntry; + }; -/******************************************************************************/ + /******************************************************************************/ -var addCookiesToDict = function(cookies) { - var i = cookies.length; - while ( i-- ) { - addCookieToDict(cookies[i]); - } -}; + var addCookiesToDict = function(cookies) { + var i = cookies.length; + while ( i-- ) { + addCookieToDict(cookies[i]); + } + }; -/******************************************************************************/ + /******************************************************************************/ -var removeCookieFromDict = function(cookieKey) { - var cookieEntry = cookieDict.get(cookieKey); - if ( cookieEntry === undefined ) { return false; } - cookieDict.delete(cookieKey); - if ( cookieEntryJunkyard.length < 25 ) { - cookieEntryJunkyard.push(cookieEntry.dispose()); - } - return true; -}; + var removeCookieFromDict = function(cookieKey) { + var cookieEntry = cookieDict.get(cookieKey); + if ( cookieEntry === undefined ) { return false; } + cookieDict.delete(cookieKey); + if ( cookieEntryJunkyard.length < 25 ) { + cookieEntryJunkyard.push(cookieEntry.dispose()); + } + return true; + }; -/******************************************************************************/ + /******************************************************************************/ + + var cookieKeyBuilder = [ + '', // 0 = scheme + '://', + '', // 2 = domain + '', // 3 = path + '{', + '', // 5 = persistent or session + '-cookie:', + '', // 7 = name + '}' + ]; + + var cookieKeyFromCookie = function(cookie) { + var cb = cookieKeyBuilder; + cb[0] = cookie.secure ? 'https' : 'http'; + cb[2] = cookie.domain.charAt(0) === '.' ? cookie.domain.slice(1) : cookie.domain; + cb[3] = cookie.path; + cb[5] = cookie.session ? 'session' : 'persistent'; + cb[7] = cookie.name; + return cb.join(''); + }; -var cookieKeyBuilder = [ - '', // 0 = scheme - '://', - '', // 2 = domain - '', // 3 = path - '{', - '', // 5 = persistent or session - '-cookie:', - '', // 7 = name - '}' -]; - -var cookieKeyFromCookie = function(cookie) { - var cb = cookieKeyBuilder; - cb[0] = cookie.secure ? 'https' : 'http'; - cb[2] = cookie.domain.charAt(0) === '.' ? cookie.domain.slice(1) : cookie.domain; - cb[3] = cookie.path; - cb[5] = cookie.session ? 'session' : 'persistent'; - cb[7] = cookie.name; - return cb.join(''); -}; - -var cookieKeyFromCookieURL = function(url, type, name) { - var ηmuri = ηm.URI.set(url); - var cb = cookieKeyBuilder; - cb[0] = ηmuri.scheme; - cb[2] = ηmuri.hostname; - cb[3] = ηmuri.path; - cb[5] = type; - cb[7] = name; - return cb.join(''); -}; + var cookieKeyFromCookieURL = function(url, type, name) { + var ηmuri = ηm.URI.set(url); + var cb = cookieKeyBuilder; + cb[0] = ηmuri.scheme; + cb[2] = ηmuri.hostname; + cb[3] = ηmuri.path; + cb[5] = type; + cb[7] = name; + return cb.join(''); + }; -/******************************************************************************/ + /******************************************************************************/ -var cookieURLFromCookieEntry = function(entry) { - if ( !entry ) { - return ''; - } - return (entry.secure ? 'https://' : 'http://') + entry.hostname + entry.path; -}; + var cookieURLFromCookieEntry = function(entry) { + if ( !entry ) { + return ''; + } + return (entry.secure ? 'https://' : 'http://') + entry.hostname + entry.path; + }; -/******************************************************************************/ + /******************************************************************************/ -var cookieMatchDomains = function(cookieKey, allHostnamesString) { - var cookieEntry = cookieDict.get(cookieKey); - if ( cookieEntry === undefined ) { return false; } - if ( allHostnamesString.indexOf(' ' + cookieEntry.hostname + ' ') < 0 ) { - if ( !cookieEntry.anySubdomain ) { - return false; + var cookieMatchDomains = function(cookieKey, allHostnamesString) { + var cookieEntry = cookieDict.get(cookieKey); + if ( cookieEntry === undefined ) { return false; } + if ( allHostnamesString.indexOf(' ' + cookieEntry.hostname + ' ') < 0 ) { + if ( !cookieEntry.anySubdomain ) { + return false; + } + if ( allHostnamesString.indexOf('.' + cookieEntry.hostname + ' ') < 0 ) { + return false; + } } - if ( allHostnamesString.indexOf('.' + cookieEntry.hostname + ' ') < 0 ) { - return false; + return true; + }; + + /******************************************************************************/ + + // Look for cookies to record for a specific web page + + var recordPageCookiesAsync = function(pageStats) { + // Store the page stats objects so that it doesn't go away + // before we handle the job. + // rhill 2013-10-19: pageStats could be nil, for example, this can + // happens if a file:// ... makes an xmlHttpRequest + if ( !pageStats ) { + return; } - } - return true; -}; + recordPageCookiesQueue.set(pageStats.pageUrl, pageStats); + if ( processPageRecordQueueTimer === null ) { + processPageRecordQueueTimer = vAPI.setTimeout(processPageRecordQueue, 1000); + } + }; -/******************************************************************************/ + /******************************************************************************/ -// Look for cookies to record for a specific web page - -var recordPageCookiesAsync = function(pageStats) { - // Store the page stats objects so that it doesn't go away - // before we handle the job. - // rhill 2013-10-19: pageStats could be nil, for example, this can - // happens if a file:// ... makes an xmlHttpRequest - if ( !pageStats ) { - return; - } - recordPageCookiesQueue.set(pageStats.pageUrl, pageStats); - if ( processPageRecordQueueTimer === null ) { - processPageRecordQueueTimer = vAPI.setTimeout(processPageRecordQueue, 1000); - } -}; + var cookieLogEntryBuilder = [ + '', + '{', + '', + '-cookie:', + '', + '}' + ]; -/******************************************************************************/ + var recordPageCookie = function(pageStore, cookieKey) { + if ( vAPI.isBehindTheSceneTabId(pageStore.tabId) ) { return; } -var cookieLogEntryBuilder = [ - '', - '{', - '', - '-cookie:', - '', - '}' -]; - -var recordPageCookie = function(pageStore, cookieKey) { - if ( vAPI.isBehindTheSceneTabId(pageStore.tabId) ) { return; } - - var cookieEntry = cookieDict.get(cookieKey); - var pageHostname = pageStore.pageHostname; - var block = ηm.mustBlock(pageHostname, cookieEntry.hostname, 'cookie'); - - cookieLogEntryBuilder[0] = cookieURLFromCookieEntry(cookieEntry); - cookieLogEntryBuilder[2] = cookieEntry.session ? 'session' : 'persistent'; - cookieLogEntryBuilder[4] = encodeURIComponent(cookieEntry.name); - - var cookieURL = cookieLogEntryBuilder.join(''); - - // rhill 2013-11-20: - // https://github.com/gorhill/httpswitchboard/issues/60 - // Need to URL-encode cookie name - pageStore.recordRequest('cookie', cookieURL, block); - ηm.logger.writeOne(pageStore.tabId, 'net', pageHostname, cookieURL, 'cookie', block); - - cookieEntry.usedOn.add(pageHostname); - - // rhill 2013-11-21: - // https://github.com/gorhill/httpswitchboard/issues/65 - // Leave alone cookies from behind-the-scene requests if - // behind-the-scene processing is disabled. - if ( !block ) { - return; - } - if ( !ηm.userSettings.deleteCookies ) { - return; - } - removeCookieAsync(cookieKey); -}; + var cookieEntry = cookieDict.get(cookieKey); + var pageHostname = pageStore.pageHostname; + var block = ηm.mustBlock(pageHostname, cookieEntry.hostname, 'cookie'); -/******************************************************************************/ + cookieLogEntryBuilder[0] = cookieURLFromCookieEntry(cookieEntry); + cookieLogEntryBuilder[2] = cookieEntry.session ? 'session' : 'persistent'; + cookieLogEntryBuilder[4] = encodeURIComponent(cookieEntry.name); -// Look for cookies to potentially remove for a specific web page - -var removePageCookiesAsync = function(pageStats) { - // Hold onto pageStats objects so that it doesn't go away - // before we handle the job. - // rhill 2013-10-19: pageStats could be nil, for example, this can - // happens if a file:// ... makes an xmlHttpRequest - if ( !pageStats ) { - return; - } - removePageCookiesQueue.set(pageStats.pageUrl, pageStats); - if ( processPageRemoveQueueTimer === null ) { - processPageRemoveQueueTimer = vAPI.setTimeout(processPageRemoveQueue, 15 * 1000); - } -}; + var cookieURL = cookieLogEntryBuilder.join(''); -/******************************************************************************/ + // rhill 2013-11-20: + // https://github.com/gorhill/httpswitchboard/issues/60 + // Need to URL-encode cookie name + pageStore.recordRequest('cookie', cookieURL, block); + ηm.logger.writeOne(pageStore.tabId, 'net', pageHostname, cookieURL, 'cookie', block); -// Candidate for removal + cookieEntry.usedOn.add(pageHostname); -var removeCookieAsync = function(cookieKey) { - removeCookieQueue.add(cookieKey); -}; + // rhill 2013-11-21: + // https://github.com/gorhill/httpswitchboard/issues/65 + // Leave alone cookies from behind-the-scene requests if + // behind-the-scene processing is disabled. + if ( !block ) { + return; + } + if ( !ηm.userSettings.deleteCookies ) { + return; + } + removeCookieAsync(cookieKey); + }; -/******************************************************************************/ + /******************************************************************************/ -var chromeCookieRemove = function(cookieEntry, name) { - var url = cookieURLFromCookieEntry(cookieEntry); - if ( url === '' ) { - return; - } - var sessionCookieKey = cookieKeyFromCookieURL(url, 'session', name); - var persistCookieKey = cookieKeyFromCookieURL(url, 'persistent', name); - var callback = function(details) { - var success = !!details; - var template = success ? i18nCookieDeleteSuccess : i18nCookieDeleteFailure; - if ( removeCookieFromDict(sessionCookieKey) ) { - if ( success ) { - ηm.cookieRemovedCounter += 1; - } - ηm.logger.writeOne('', 'info', 'cookie', template.replace('{{value}}', sessionCookieKey)); + // Look for cookies to potentially remove for a specific web page + + var removePageCookiesAsync = function(pageStats) { + // Hold onto pageStats objects so that it doesn't go away + // before we handle the job. + // rhill 2013-10-19: pageStats could be nil, for example, this can + // happens if a file:// ... makes an xmlHttpRequest + if ( !pageStats ) { + return; } - if ( removeCookieFromDict(persistCookieKey) ) { - if ( success ) { - ηm.cookieRemovedCounter += 1; - } - ηm.logger.writeOne('', 'info', 'cookie', template.replace('{{value}}', persistCookieKey)); + removePageCookiesQueue.set(pageStats.pageUrl, pageStats); + if ( processPageRemoveQueueTimer === null ) { + processPageRemoveQueueTimer = vAPI.setTimeout(processPageRemoveQueue, 15 * 1000); } }; - vAPI.cookies.remove({ url: url, name: name }, callback); -}; + /******************************************************************************/ -var i18nCookieDeleteSuccess = vAPI.i18n('loggerEntryCookieDeleted'); -var i18nCookieDeleteFailure = vAPI.i18n('loggerEntryDeleteCookieError'); + // Candidate for removal -/******************************************************************************/ + var removeCookieAsync = function(cookieKey) { + removeCookieQueue.add(cookieKey); + }; -var processPageRecordQueue = function() { - processPageRecordQueueTimer = null; + /******************************************************************************/ - for ( var pageStore of recordPageCookiesQueue.values() ) { - findAndRecordPageCookies(pageStore); - } - recordPageCookiesQueue.clear(); -}; + var chromeCookieRemove = function(cookieEntry, name) { + var url = cookieURLFromCookieEntry(cookieEntry); + if ( url === '' ) { + return; + } + var sessionCookieKey = cookieKeyFromCookieURL(url, 'session', name); + var persistCookieKey = cookieKeyFromCookieURL(url, 'persistent', name); + var callback = function(details) { + var success = !!details; + var template = success ? i18nCookieDeleteSuccess : i18nCookieDeleteFailure; + if ( removeCookieFromDict(sessionCookieKey) ) { + if ( success ) { + ηm.cookieRemovedCounter += 1; + } + ηm.logger.writeOne('', 'info', 'cookie', template.replace('{{value}}', sessionCookieKey)); + } + if ( removeCookieFromDict(persistCookieKey) ) { + if ( success ) { + ηm.cookieRemovedCounter += 1; + } + ηm.logger.writeOne('', 'info', 'cookie', template.replace('{{value}}', persistCookieKey)); + } + }; -/******************************************************************************/ + vAPI.cookies.remove({ url: url, name: name }, callback); + }; -var processPageRemoveQueue = function() { - processPageRemoveQueueTimer = null; + var i18nCookieDeleteSuccess = vAPI.i18n('loggerEntryCookieDeleted'); + var i18nCookieDeleteFailure = vAPI.i18n('loggerEntryDeleteCookieError'); - for ( var pageStore of removePageCookiesQueue.values() ) { - findAndRemovePageCookies(pageStore); - } - removePageCookiesQueue.clear(); -}; + /******************************************************************************/ -/******************************************************************************/ + var processPageRecordQueue = function() { + processPageRecordQueueTimer = null; -// Effectively remove cookies. + for ( var pageStore of recordPageCookiesQueue.values() ) { + findAndRecordPageCookies(pageStore); + } + recordPageCookiesQueue.clear(); + }; -var processRemoveQueue = function() { - var userSettings = ηm.userSettings; - var deleteCookies = userSettings.deleteCookies; + /******************************************************************************/ - // Session cookies which timestamp is *after* tstampObsolete will - // be left untouched - // https://github.com/gorhill/httpswitchboard/issues/257 - var tstampObsolete = userSettings.deleteUnusedSessionCookies ? - Date.now() - userSettings.deleteUnusedSessionCookiesAfter * 60 * 1000 : - 0; + var processPageRemoveQueue = function() { + processPageRemoveQueueTimer = null; - var srcHostnames; - var cookieEntry; + for ( var pageStore of removePageCookiesQueue.values() ) { + findAndRemovePageCookies(pageStore); + } + removePageCookiesQueue.clear(); + }; - for ( var cookieKey of removeCookieQueue ) { - // rhill 2014-05-12: Apparently this can happen. I have to - // investigate how (A session cookie has same name as a - // persistent cookie?) - cookieEntry = cookieDict.get(cookieKey); - if ( cookieEntry === undefined ) { continue; } + /******************************************************************************/ + + // Effectively remove cookies. + + var processRemoveQueue = function() { + var userSettings = ηm.userSettings; + var deleteCookies = userSettings.deleteCookies; + + // Session cookies which timestamp is *after* tstampObsolete will + // be left untouched + // https://github.com/gorhill/httpswitchboard/issues/257 + var tstampObsolete = userSettings.deleteUnusedSessionCookies ? + Date.now() - userSettings.deleteUnusedSessionCookiesAfter * 60 * 1000 : + 0; + + var srcHostnames; + var cookieEntry; + + for ( var cookieKey of removeCookieQueue ) { + // rhill 2014-05-12: Apparently this can happen. I have to + // investigate how (A session cookie has same name as a + // persistent cookie?) + cookieEntry = cookieDict.get(cookieKey); + if ( cookieEntry === undefined ) { continue; } + + // Delete obsolete session cookies: enabled. + if ( tstampObsolete !== 0 && cookieEntry.session ) { + if ( cookieEntry.tstamp < tstampObsolete ) { + chromeCookieRemove(cookieEntry, cookieEntry.name); + continue; + } + } - // Delete obsolete session cookies: enabled. - if ( tstampObsolete !== 0 && cookieEntry.session ) { - if ( cookieEntry.tstamp < tstampObsolete ) { - chromeCookieRemove(cookieEntry, cookieEntry.name); + // Delete all blocked cookies: disabled. + if ( deleteCookies === false ) { continue; } - } - - // Delete all blocked cookies: disabled. - if ( deleteCookies === false ) { - continue; - } - // Query scopes only if we are going to use them - if ( srcHostnames === undefined ) { - srcHostnames = ηm.tMatrix.extractAllSourceHostnames(); - } + // Query scopes only if we are going to use them + if ( srcHostnames === undefined ) { + srcHostnames = ηm.tMatrix.extractAllSourceHostnames(); + } - // Ensure cookie is not allowed on ALL current web pages: It can - // happen that a cookie is blacklisted on one web page while - // being whitelisted on another (because of per-page permissions). - if ( canRemoveCookie(cookieKey, srcHostnames) ) { - chromeCookieRemove(cookieEntry, cookieEntry.name); + // Ensure cookie is not allowed on ALL current web pages: It can + // happen that a cookie is blacklisted on one web page while + // being whitelisted on another (because of per-page permissions). + if ( canRemoveCookie(cookieKey, srcHostnames) ) { + chromeCookieRemove(cookieEntry, cookieEntry.name); + } } - } - removeCookieQueue.clear(); + removeCookieQueue.clear(); - vAPI.setTimeout(processRemoveQueue, processRemoveQueuePeriod); -}; - -/******************************************************************************/ + vAPI.setTimeout(processRemoveQueue, processRemoveQueuePeriod); + }; -// Once in a while, we go ahead and clean everything that might have been -// left behind. - -// Remove only some of the cookies which are candidate for removal: who knows, -// maybe a user has 1000s of cookies sitting in his browser... - -var processClean = function() { - var us = ηm.userSettings; - if ( us.deleteCookies || us.deleteUnusedSessionCookies ) { - var cookieKeys = Array.from(cookieDict.keys()), - len = cookieKeys.length, - step, offset, n; - if ( len > 25 ) { - step = len / 25; - offset = Math.floor(Math.random() * len); - n = 25; - } else { - step = 1; - offset = 0; - n = len; - } - var i = offset; - while ( n-- ) { - removeCookieAsync(cookieKeys[Math.floor(i % len)]); - i += step; + /******************************************************************************/ + + // Once in a while, we go ahead and clean everything that might have been + // left behind. + + // Remove only some of the cookies which are candidate for removal: who knows, + // maybe a user has 1000s of cookies sitting in his browser... + + var processClean = function() { + var us = ηm.userSettings; + if ( us.deleteCookies || us.deleteUnusedSessionCookies ) { + var cookieKeys = Array.from(cookieDict.keys()), + len = cookieKeys.length, + step, offset, n; + if ( len > 25 ) { + step = len / 25; + offset = Math.floor(Math.random() * len); + n = 25; + } else { + step = 1; + offset = 0; + n = len; + } + var i = offset; + while ( n-- ) { + removeCookieAsync(cookieKeys[Math.floor(i % len)]); + i += step; + } } - } - vAPI.setTimeout(processClean, processCleanPeriod); -}; + vAPI.setTimeout(processClean, processCleanPeriod); + }; -/******************************************************************************/ + /******************************************************************************/ -var findAndRecordPageCookies = function(pageStore) { - for ( var cookieKey of cookieDict.keys() ) { - if ( cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) { - recordPageCookie(pageStore, cookieKey); + var findAndRecordPageCookies = function(pageStore) { + for ( var cookieKey of cookieDict.keys() ) { + if ( cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) { + recordPageCookie(pageStore, cookieKey); + } } - } -}; + }; -/******************************************************************************/ + /******************************************************************************/ -var findAndRemovePageCookies = function(pageStore) { - for ( var cookieKey of cookieDict.keys() ) { - if ( cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) { - removeCookieAsync(cookieKey); + var findAndRemovePageCookies = function(pageStore) { + for ( var cookieKey of cookieDict.keys() ) { + if ( cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) { + removeCookieAsync(cookieKey); + } } - } -}; + }; -/******************************************************************************/ + /******************************************************************************/ -var canRemoveCookie = function(cookieKey, srcHostnames) { - var cookieEntry = cookieDict.get(cookieKey); - if ( cookieEntry === undefined ) { return false; } + var canRemoveCookie = function(cookieKey, srcHostnames) { + var cookieEntry = cookieDict.get(cookieKey); + if ( cookieEntry === undefined ) { return false; } - var cookieHostname = cookieEntry.hostname; - var srcHostname; + var cookieHostname = cookieEntry.hostname; + var srcHostname; - for ( srcHostname of cookieEntry.usedOn ) { - if ( ηm.mustAllow(srcHostname, cookieHostname, 'cookie') ) { - return false; - } - } - // Maybe there is a scope in which the cookie is 1st-party-allowed. - // For example, if I am logged in into `github.com`, I do not want to be - // logged out just because I did not yet open a `github.com` page after - // re-starting the browser. - srcHostname = cookieHostname; - var pos; - for (;;) { - if ( srcHostnames.has(srcHostname) ) { + for ( srcHostname of cookieEntry.usedOn ) { if ( ηm.mustAllow(srcHostname, cookieHostname, 'cookie') ) { return false; } } - if ( srcHostname === cookieEntry.domain ) { - break; - } - pos = srcHostname.indexOf('.'); - if ( pos === -1 ) { - break; + // Maybe there is a scope in which the cookie is 1st-party-allowed. + // For example, if I am logged in into `github.com`, I do not want to be + // logged out just because I did not yet open a `github.com` page after + // re-starting the browser. + srcHostname = cookieHostname; + var pos; + for (;;) { + if ( srcHostnames.has(srcHostname) ) { + if ( ηm.mustAllow(srcHostname, cookieHostname, 'cookie') ) { + return false; + } + } + if ( srcHostname === cookieEntry.domain ) { + break; + } + pos = srcHostname.indexOf('.'); + if ( pos === -1 ) { + break; + } + srcHostname = srcHostname.slice(pos + 1); } - srcHostname = srcHostname.slice(pos + 1); - } - return true; -}; + return true; + }; -/******************************************************************************/ + /******************************************************************************/ + + // Listen to any change in cookieland, we will update page stats accordingly. -// Listen to any change in cookieland, we will update page stats accordingly. - -vAPI.cookies.onChanged = function(cookie) { - // rhill 2013-12-11: If cookie value didn't change, no need to record. - // https://github.com/gorhill/httpswitchboard/issues/79 - var cookieKey = cookieKeyFromCookie(cookie); - var cookieEntry = cookieDict.get(cookieKey); - if ( cookieEntry === undefined ) { - cookieEntry = addCookieToDict(cookie); - } else { - cookieEntry.tstamp = Date.now(); - if ( cookie.value === cookieEntry.value ) { return; } - cookieEntry.value = cookie.value; - } - - // Go through all pages and update if needed, as one cookie can be used - // by many web pages, so they need to be recorded for all these pages. - var pageStores = ηm.pageStores; - var pageStore; - for ( var tabId in pageStores ) { - if ( pageStores.hasOwnProperty(tabId) === false ) { - continue; + vAPI.cookies.onChanged = function(cookie) { + // rhill 2013-12-11: If cookie value didn't change, no need to record. + // https://github.com/gorhill/httpswitchboard/issues/79 + var cookieKey = cookieKeyFromCookie(cookie); + var cookieEntry = cookieDict.get(cookieKey); + if ( cookieEntry === undefined ) { + cookieEntry = addCookieToDict(cookie); + } else { + cookieEntry.tstamp = Date.now(); + if ( cookie.value === cookieEntry.value ) { return; } + cookieEntry.value = cookie.value; } - pageStore = pageStores[tabId]; - if ( !cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) { - continue; + + // Go through all pages and update if needed, as one cookie can be used + // by many web pages, so they need to be recorded for all these pages. + var pageStores = ηm.pageStores; + var pageStore; + for ( var tabId in pageStores ) { + if ( pageStores.hasOwnProperty(tabId) === false ) { + continue; + } + pageStore = pageStores[tabId]; + if ( !cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) { + continue; + } + recordPageCookie(pageStore, cookieKey); } - recordPageCookie(pageStore, cookieKey); - } -}; + }; -/******************************************************************************/ + /******************************************************************************/ -// Listen to any change in cookieland, we will update page stats accordingly. + // Listen to any change in cookieland, we will update page stats accordingly. -vAPI.cookies.onRemoved = function(cookie) { - var cookieKey = cookieKeyFromCookie(cookie); - if ( removeCookieFromDict(cookieKey) ) { - ηm.logger.writeOne('', 'info', 'cookie', i18nCookieDeleteSuccess.replace('{{value}}', cookieKey)); - } -}; + vAPI.cookies.onRemoved = function(cookie) { + var cookieKey = cookieKeyFromCookie(cookie); + if ( removeCookieFromDict(cookieKey) ) { + ηm.logger.writeOne('', 'info', 'cookie', i18nCookieDeleteSuccess.replace('{{value}}', cookieKey)); + } + }; -/******************************************************************************/ + /******************************************************************************/ -// Listen to any change in cookieland, we will update page stats accordingly. + // Listen to any change in cookieland, we will update page stats accordingly. -vAPI.cookies.onAllRemoved = function() { - for ( var cookieKey of cookieDict.keys() ) { - if ( removeCookieFromDict(cookieKey) ) { - ηm.logger.writeOne('', 'info', 'cookie', i18nCookieDeleteSuccess.replace('{{value}}', cookieKey)); + vAPI.cookies.onAllRemoved = function() { + for ( var cookieKey of cookieDict.keys() ) { + if ( removeCookieFromDict(cookieKey) ) { + ηm.logger.writeOne('', 'info', 'cookie', i18nCookieDeleteSuccess.replace('{{value}}', cookieKey)); + } } - } -}; + }; -/******************************************************************************/ + /******************************************************************************/ -vAPI.cookies.getAll(addCookiesToDict); -vAPI.cookies.start(); + vAPI.cookies.getAll(addCookiesToDict); + vAPI.cookies.start(); -vAPI.setTimeout(processRemoveQueue, processRemoveQueuePeriod); -vAPI.setTimeout(processClean, processCleanPeriod); + vAPI.setTimeout(processRemoveQueue, processRemoveQueuePeriod); + vAPI.setTimeout(processClean, processCleanPeriod); -/******************************************************************************/ + /******************************************************************************/ -// Expose only what is necessary + // Expose only what is necessary -return { - recordPageCookies: recordPageCookiesAsync, - removePageCookies: removePageCookiesAsync -}; + return { + recordPageCookies: recordPageCookiesAsync, + removePageCookies: removePageCookiesAsync + }; -/******************************************************************************/ + /******************************************************************************/ })(); /******************************************************************************/ - diff --git a/js/dashboard-common.js b/js/dashboard-common.js index abb53b6..68ba7d5 100644 --- a/js/dashboard-common.js +++ b/js/dashboard-common.js @@ -27,18 +27,18 @@ uDom.onLoad(function() { -/******************************************************************************/ + /******************************************************************************/ -// Open links in the proper window -uDom('a').attr('target', '_blank'); -uDom('a[href*="dashboard.html"]').attr('target', '_parent'); -uDom('.whatisthis').on('click', function() { - uDom(this).parent() - .descendants('.whatisthis-expandable') - .toggleClass('whatisthis-expanded'); -}); + // Open links in the proper window + uDom('a').attr('target', '_blank'); + uDom('a[href*="dashboard.html"]').attr('target', '_parent'); + uDom('.whatisthis').on('click', function() { + uDom(this).parent() + .descendants('.whatisthis-expandable') + .toggleClass('whatisthis-expanded'); + }); -/******************************************************************************/ + /******************************************************************************/ }); diff --git a/js/hosts-files.js b/js/hosts-files.js index de7f1ee..8c65648 100644 --- a/js/hosts-files.js +++ b/js/hosts-files.js @@ -29,364 +29,363 @@ (function() { -/******************************************************************************/ - -var listDetails = {}, - lastUpdateTemplateString = vAPI.i18n('hostsFilesLastUpdate'), - hostsFilesSettingsHash, - reValidExternalList = /[a-z-]+:\/\/\S*\/\S+/; - -/******************************************************************************/ - -vAPI.messaging.addListener(function onMessage(msg) { - switch ( msg.what ) { - case 'assetUpdated': - updateAssetStatus(msg); - break; - case 'assetsUpdated': - document.body.classList.remove('updating'); - break; - case 'loadHostsFilesCompleted': - renderHostsFiles(); - break; - default: - break; - } -}); - -/******************************************************************************/ - -var renderNumber = function(value) { - return value.toLocaleString(); -}; + /******************************************************************************/ + + var listDetails = {}, + lastUpdateTemplateString = vAPI.i18n('hostsFilesLastUpdate'), + hostsFilesSettingsHash, + reValidExternalList = /[a-z-]+:\/\/\S*\/\S+/; + + /******************************************************************************/ + + vAPI.messaging.addListener(function onMessage(msg) { + switch ( msg.what ) { + case 'assetUpdated': + updateAssetStatus(msg); + break; + case 'assetsUpdated': + document.body.classList.remove('updating'); + break; + case 'loadHostsFilesCompleted': + renderHostsFiles(); + break; + default: + break; + } + }); -/******************************************************************************/ + /******************************************************************************/ -var renderHostsFiles = function(soft) { - var listEntryTemplate = uDom('#templates .listEntry'), - listStatsTemplate = vAPI.i18n('hostsFilesPerFileStats'), - renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString, - reExternalHostFile = /^https?:/; - - // Assemble a pretty list name if possible - var listNameFromListKey = function(listKey) { - var list = listDetails.current[listKey] || listDetails.available[listKey]; - var listTitle = list ? list.title : ''; - if ( listTitle === '' ) { return listKey; } - return listTitle; + var renderNumber = function(value) { + return value.toLocaleString(); }; - var liFromListEntry = function(listKey, li) { - var entry = listDetails.available[listKey], - elem; - if ( !li ) { - li = listEntryTemplate.clone().nodeAt(0); - } - if ( li.getAttribute('data-listkey') !== listKey ) { - li.setAttribute('data-listkey', listKey); - elem = li.querySelector('input[type="checkbox"]'); - elem.checked = entry.off !== true; - elem = li.querySelector('a:nth-of-type(1)'); - elem.setAttribute('href', 'asset-viewer.html?url=' + encodeURI(listKey)); - elem.setAttribute('type', 'text/html'); - elem.textContent = listNameFromListKey(listKey); - li.classList.remove('toRemove'); - if ( entry.supportName ) { - li.classList.add('support'); - elem = li.querySelector('a.support'); - elem.setAttribute('href', entry.supportURL); - elem.setAttribute('title', entry.supportName); - } else { - li.classList.remove('support'); + /******************************************************************************/ + + var renderHostsFiles = function(soft) { + var listEntryTemplate = uDom('#templates .listEntry'), + listStatsTemplate = vAPI.i18n('hostsFilesPerFileStats'), + renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString, + reExternalHostFile = /^https?:/; + + // Assemble a pretty list name if possible + var listNameFromListKey = function(listKey) { + var list = listDetails.current[listKey] || listDetails.available[listKey]; + var listTitle = list ? list.title : ''; + if ( listTitle === '' ) { return listKey; } + return listTitle; + }; + + var liFromListEntry = function(listKey, li) { + var entry = listDetails.available[listKey], + elem; + if ( !li ) { + li = listEntryTemplate.clone().nodeAt(0); } - if ( entry.external ) { - li.classList.add('external'); - } else { - li.classList.remove('external'); + if ( li.getAttribute('data-listkey') !== listKey ) { + li.setAttribute('data-listkey', listKey); + elem = li.querySelector('input[type="checkbox"]'); + elem.checked = entry.off !== true; + elem = li.querySelector('a:nth-of-type(1)'); + elem.setAttribute('href', 'asset-viewer.html?url=' + encodeURI(listKey)); + elem.setAttribute('type', 'text/html'); + elem.textContent = listNameFromListKey(listKey); + li.classList.remove('toRemove'); + if ( entry.supportName ) { + li.classList.add('support'); + elem = li.querySelector('a.support'); + elem.setAttribute('href', entry.supportURL); + elem.setAttribute('title', entry.supportName); + } else { + li.classList.remove('support'); + } + if ( entry.external ) { + li.classList.add('external'); + } else { + li.classList.remove('external'); + } + if ( entry.instructionURL ) { + li.classList.add('mustread'); + elem = li.querySelector('a.mustread'); + elem.setAttribute('href', entry.instructionURL); + } else { + li.classList.remove('mustread'); + } } - if ( entry.instructionURL ) { - li.classList.add('mustread'); - elem = li.querySelector('a.mustread'); - elem.setAttribute('href', entry.instructionURL); - } else { - li.classList.remove('mustread'); + // https://github.com/gorhill/uBlock/issues/1429 + if ( !soft ) { + elem = li.querySelector('input[type="checkbox"]'); + elem.checked = entry.off !== true; } - } - // https://github.com/gorhill/uBlock/issues/1429 - if ( !soft ) { - elem = li.querySelector('input[type="checkbox"]'); - elem.checked = entry.off !== true; - } - elem = li.querySelector('span.counts'); - var text = ''; - if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) { - text = listStatsTemplate - .replace('{{used}}', renderNumber(entry.off ? 0 : entry.entryUsedCount)) - .replace('{{total}}', renderNumber(entry.entryCount)); - } - elem.textContent = text; - // https://github.com/chrisaljoudi/uBlock/issues/104 - var asset = listDetails.cache[listKey] || {}; - var remoteURL = asset.remoteURL; - li.classList.toggle( - 'unsecure', - typeof remoteURL === 'string' && remoteURL.lastIndexOf('http:', 0) === 0 - ); - li.classList.toggle('failed', asset.error !== undefined); - li.classList.toggle('obsolete', asset.obsolete === true); - li.classList.toggle('cached', asset.cached === true && asset.writeTime > 0); - if ( asset.cached ) { - li.querySelector('.status.cache').setAttribute( - 'title', - lastUpdateTemplateString.replace('{{ago}}', renderElapsedTimeToString(asset.writeTime)) + elem = li.querySelector('span.counts'); + var text = ''; + if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) { + text = listStatsTemplate + .replace('{{used}}', renderNumber(entry.off ? 0 : entry.entryUsedCount)) + .replace('{{total}}', renderNumber(entry.entryCount)); + } + elem.textContent = text; + // https://github.com/chrisaljoudi/uBlock/issues/104 + var asset = listDetails.cache[listKey] || {}; + var remoteURL = asset.remoteURL; + li.classList.toggle( + 'unsecure', + typeof remoteURL === 'string' && remoteURL.lastIndexOf('http:', 0) === 0 ); - } - li.classList.remove('discard'); - return li; - }; + li.classList.toggle('failed', asset.error !== undefined); + li.classList.toggle('obsolete', asset.obsolete === true); + li.classList.toggle('cached', asset.cached === true && asset.writeTime > 0); + if ( asset.cached ) { + li.querySelector('.status.cache').setAttribute( + 'title', + lastUpdateTemplateString.replace('{{ago}}', renderElapsedTimeToString(asset.writeTime)) + ); + } + li.classList.remove('discard'); + return li; + }; + + var onListsReceived = function(details) { + // Before all, set context vars + listDetails = details; + + // Incremental rendering: this will allow us to easily discard unused + // DOM list entries. + uDom('#lists .listEntry').addClass('discard'); + + var availableLists = details.available, + listKeys = Object.keys(details.available); + + // Sort works this way: + // - Send /^https?:/ items at the end (custom hosts file URL) + listKeys.sort(function(a, b) { + var ta = availableLists[a].title || a, + tb = availableLists[b].title || b; + if ( reExternalHostFile.test(ta) === reExternalHostFile.test(tb) ) { + return ta.localeCompare(tb); + } + return reExternalHostFile.test(tb) ? -1 : 1; + }); + + var ulList = document.querySelector('#lists'); + for ( var i = 0; i < listKeys.length; i++ ) { + var liEntry = liFromListEntry(listKeys[i], ulList.children[i]); + if ( liEntry.parentElement === null ) { + ulList.appendChild(liEntry); + } + } - var onListsReceived = function(details) { - // Before all, set context vars - listDetails = details; + uDom('#lists .listEntry.discard').remove(); + uDom('#listsOfBlockedHostsPrompt').text( + vAPI.i18n('hostsFilesStats').replace( + '{{blockedHostnameCount}}', + renderNumber(details.blockedHostnameCount) + ) + ); + uDom('#autoUpdate').prop('checked', listDetails.autoUpdate === true); - // Incremental rendering: this will allow us to easily discard unused - // DOM list entries. - uDom('#lists .listEntry').addClass('discard'); + if ( !soft ) { + hostsFilesSettingsHash = hashFromCurrentFromSettings(); + } + renderWidgets(); + }; - var availableLists = details.available, - listKeys = Object.keys(details.available); + vAPI.messaging.send('hosts-files.js', { what: 'getLists' }, onListsReceived); + }; - // Sort works this way: - // - Send /^https?:/ items at the end (custom hosts file URL) - listKeys.sort(function(a, b) { - var ta = availableLists[a].title || a, - tb = availableLists[b].title || b; - if ( reExternalHostFile.test(ta) === reExternalHostFile.test(tb) ) { - return ta.localeCompare(tb); - } - return reExternalHostFile.test(tb) ? -1 : 1; - }); + /******************************************************************************/ - var ulList = document.querySelector('#lists'); - for ( var i = 0; i < listKeys.length; i++ ) { - var liEntry = liFromListEntry(listKeys[i], ulList.children[i]); - if ( liEntry.parentElement === null ) { - ulList.appendChild(liEntry); - } - } + var renderWidgets = function() { + uDom('#buttonUpdate').toggleClass('disabled', document.querySelector('body:not(.updating) #lists .listEntry.obsolete > input[type="checkbox"]:checked') === null); + uDom('#buttonPurgeAll').toggleClass('disabled', document.querySelector('#lists .listEntry.cached') === null); + uDom('#buttonApply').toggleClass('disabled', hostsFilesSettingsHash === hashFromCurrentFromSettings()); + }; - uDom('#lists .listEntry.discard').remove(); - uDom('#listsOfBlockedHostsPrompt').text( - vAPI.i18n('hostsFilesStats').replace( - '{{blockedHostnameCount}}', - renderNumber(details.blockedHostnameCount) - ) - ); - uDom('#autoUpdate').prop('checked', listDetails.autoUpdate === true); + /******************************************************************************/ - if ( !soft ) { - hostsFilesSettingsHash = hashFromCurrentFromSettings(); + var updateAssetStatus = function(details) { + var li = document.querySelector('#lists .listEntry[data-listkey="' + details.key + '"]'); + if ( li === null ) { return; } + li.classList.toggle('failed', !!details.failed); + li.classList.toggle('obsolete', !details.cached); + li.classList.toggle('cached', !!details.cached); + if ( details.cached ) { + li.querySelector('.status.cache').setAttribute( + 'title', + lastUpdateTemplateString.replace( + '{{ago}}', + vAPI.i18n.renderElapsedTimeToString(Date.now()) + ) + ); } renderWidgets(); }; - vAPI.messaging.send('hosts-files.js', { what: 'getLists' }, onListsReceived); -}; - -/******************************************************************************/ - -var renderWidgets = function() { - uDom('#buttonUpdate').toggleClass('disabled', document.querySelector('body:not(.updating) #lists .listEntry.obsolete > input[type="checkbox"]:checked') === null); - uDom('#buttonPurgeAll').toggleClass('disabled', document.querySelector('#lists .listEntry.cached') === null); - uDom('#buttonApply').toggleClass('disabled', hostsFilesSettingsHash === hashFromCurrentFromSettings()); -}; - -/******************************************************************************/ - -var updateAssetStatus = function(details) { - var li = document.querySelector('#lists .listEntry[data-listkey="' + details.key + '"]'); - if ( li === null ) { return; } - li.classList.toggle('failed', !!details.failed); - li.classList.toggle('obsolete', !details.cached); - li.classList.toggle('cached', !!details.cached); - if ( details.cached ) { - li.querySelector('.status.cache').setAttribute( - 'title', - lastUpdateTemplateString.replace( - '{{ago}}', - vAPI.i18n.renderElapsedTimeToString(Date.now()) - ) - ); - } - renderWidgets(); -}; - -/******************************************************************************* + /******************************************************************************* Compute a hash from all the settings affecting how filter lists are loaded in memory. -**/ - -var hashFromCurrentFromSettings = function() { - var hash = [], - listHash = [], - listEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'), - liEntry, - i = listEntries.length; - while ( i-- ) { - liEntry = listEntries[i]; - if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) { - listHash.push(liEntry.getAttribute('data-listkey')); + **/ + + var hashFromCurrentFromSettings = function() { + var hash = [], + listHash = [], + listEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'), + liEntry, + i = listEntries.length; + while ( i-- ) { + liEntry = listEntries[i]; + if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) { + listHash.push(liEntry.getAttribute('data-listkey')); + } } - } - hash.push( - listHash.sort().join(), - reValidExternalList.test(document.getElementById('externalHostsFiles').value), - document.querySelector('#lists .listEntry.toRemove') !== null - ); - return hash.join(); -}; + hash.push( + listHash.sort().join(), + reValidExternalList.test(document.getElementById('externalHostsFiles').value), + document.querySelector('#lists .listEntry.toRemove') !== null + ); + return hash.join(); + }; -/******************************************************************************/ + /******************************************************************************/ -var onHostsFilesSettingsChanged = function() { - renderWidgets(); -}; + var onHostsFilesSettingsChanged = function() { + renderWidgets(); + }; -/******************************************************************************/ + /******************************************************************************/ -var onRemoveExternalHostsFile = function(ev) { - var liEntry = uDom(this).ancestors('[data-listkey]'), - listKey = liEntry.attr('data-listkey'); - if ( listKey ) { - liEntry.toggleClass('toRemove'); - renderWidgets(); - } - ev.preventDefault(); -}; + var onRemoveExternalHostsFile = function(ev) { + var liEntry = uDom(this).ancestors('[data-listkey]'), + listKey = liEntry.attr('data-listkey'); + if ( listKey ) { + liEntry.toggleClass('toRemove'); + renderWidgets(); + } + ev.preventDefault(); + }; -/******************************************************************************/ + /******************************************************************************/ -var onPurgeClicked = function() { - var button = uDom(this), - liEntry = button.ancestors('[data-listkey]'), - listKey = liEntry.attr('data-listkey'); - if ( !listKey ) { return; } + var onPurgeClicked = function() { + var button = uDom(this), + liEntry = button.ancestors('[data-listkey]'), + listKey = liEntry.attr('data-listkey'); + if ( !listKey ) { return; } - vAPI.messaging.send('hosts-files.js', { what: 'purgeCache', assetKey: listKey }); - liEntry.addClass('obsolete'); - liEntry.removeClass('cached'); + vAPI.messaging.send('hosts-files.js', { what: 'purgeCache', assetKey: listKey }); + liEntry.addClass('obsolete'); + liEntry.removeClass('cached'); - if ( liEntry.descendants('input').first().prop('checked') ) { - renderWidgets(); - } -}; + if ( liEntry.descendants('input').first().prop('checked') ) { + renderWidgets(); + } + }; -/******************************************************************************/ + /******************************************************************************/ + + var selectHostsFiles = function(callback) { + // Hosts files to select + var toSelect = [], + liEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'), + i = liEntries.length, + liEntry; + while ( i-- ) { + liEntry = liEntries[i]; + if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) { + toSelect.push(liEntry.getAttribute('data-listkey')); + } + } -var selectHostsFiles = function(callback) { - // Hosts files to select - var toSelect = [], - liEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'), - i = liEntries.length, - liEntry; - while ( i-- ) { - liEntry = liEntries[i]; - if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) { - toSelect.push(liEntry.getAttribute('data-listkey')); + // External hosts files to remove + var toRemove = []; + liEntries = document.querySelectorAll('#lists .listEntry.toRemove[data-listkey]'); + i = liEntries.length; + while ( i-- ) { + toRemove.push(liEntries[i].getAttribute('data-listkey')); } - } - - // External hosts files to remove - var toRemove = []; - liEntries = document.querySelectorAll('#lists .listEntry.toRemove[data-listkey]'); - i = liEntries.length; - while ( i-- ) { - toRemove.push(liEntries[i].getAttribute('data-listkey')); - } - - // External hosts files to import - var externalListsElem = document.getElementById('externalHostsFiles'), - toImport = externalListsElem.value.trim(); - externalListsElem.value = ''; - - vAPI.messaging.send( - 'hosts-files.js', - { - what: 'selectHostsFiles', - toSelect: toSelect, - toImport: toImport, - toRemove: toRemove - }, - callback - ); - - hostsFilesSettingsHash = hashFromCurrentFromSettings(); -}; -/******************************************************************************/ + // External hosts files to import + var externalListsElem = document.getElementById('externalHostsFiles'), + toImport = externalListsElem.value.trim(); + externalListsElem.value = ''; + + vAPI.messaging.send( + 'hosts-files.js', + { + what: 'selectHostsFiles', + toSelect: toSelect, + toImport: toImport, + toRemove: toRemove + }, + callback + ); -var buttonApplyHandler = function() { - uDom('#buttonApply').removeClass('enabled'); - selectHostsFiles(function() { - vAPI.messaging.send('hosts-files.js', { what: 'reloadHostsFiles' }); - }); - renderWidgets(); -}; + hostsFilesSettingsHash = hashFromCurrentFromSettings(); + }; -/******************************************************************************/ + /******************************************************************************/ -var buttonUpdateHandler = function() { - uDom('#buttonUpdate').removeClass('enabled'); - selectHostsFiles(function() { - document.body.classList.add('updating'); - vAPI.messaging.send('hosts-files.js', { what: 'forceUpdateAssets' }); + var buttonApplyHandler = function() { + uDom('#buttonApply').removeClass('enabled'); + selectHostsFiles(function() { + vAPI.messaging.send('hosts-files.js', { what: 'reloadHostsFiles' }); + }); renderWidgets(); - }); - renderWidgets(); -}; + }; -/******************************************************************************/ + /******************************************************************************/ -var buttonPurgeAllHandler = function() { - uDom('#buttonPurgeAll').removeClass('enabled'); - vAPI.messaging.send( - 'hosts-files.js', - { what: 'purgeAllCaches' }, - function() { - renderHostsFiles(true); - } - ); -}; + var buttonUpdateHandler = function() { + uDom('#buttonUpdate').removeClass('enabled'); + selectHostsFiles(function() { + document.body.classList.add('updating'); + vAPI.messaging.send('hosts-files.js', { what: 'forceUpdateAssets' }); + renderWidgets(); + }); + renderWidgets(); + }; -/******************************************************************************/ + /******************************************************************************/ -var autoUpdateCheckboxChanged = function() { - vAPI.messaging.send( - 'hosts-files.js', - { - what: 'userSettings', - name: 'autoUpdate', - value: this.checked - } - ); -}; + var buttonPurgeAllHandler = function() { + uDom('#buttonPurgeAll').removeClass('enabled'); + vAPI.messaging.send( + 'hosts-files.js', + { what: 'purgeAllCaches' }, + function() { + renderHostsFiles(true); + } + ); + }; -/******************************************************************************/ + /******************************************************************************/ + + var autoUpdateCheckboxChanged = function() { + vAPI.messaging.send( + 'hosts-files.js', + { + what: 'userSettings', + name: 'autoUpdate', + value: this.checked + } + ); + }; -uDom('#autoUpdate').on('change', autoUpdateCheckboxChanged); -uDom('#buttonApply').on('click', buttonApplyHandler); -uDom('#buttonUpdate').on('click', buttonUpdateHandler); -uDom('#buttonPurgeAll').on('click', buttonPurgeAllHandler); -uDom('#lists').on('change', '.listEntry > input', onHostsFilesSettingsChanged); -uDom('#lists').on('click', '.listEntry > a.remove', onRemoveExternalHostsFile); -uDom('#lists').on('click', 'span.cache', onPurgeClicked); -uDom('#externalHostsFiles').on('input', onHostsFilesSettingsChanged); + /******************************************************************************/ -renderHostsFiles(); + uDom('#autoUpdate').on('change', autoUpdateCheckboxChanged); + uDom('#buttonApply').on('click', buttonApplyHandler); + uDom('#buttonUpdate').on('click', buttonUpdateHandler); + uDom('#buttonPurgeAll').on('click', buttonPurgeAllHandler); + uDom('#lists').on('change', '.listEntry > input', onHostsFilesSettingsChanged); + uDom('#lists').on('click', '.listEntry > a.remove', onRemoveExternalHostsFile); + uDom('#lists').on('click', 'span.cache', onPurgeClicked); + uDom('#externalHostsFiles').on('input', onHostsFilesSettingsChanged); -/******************************************************************************/ + renderHostsFiles(); -})(); + /******************************************************************************/ +})(); @@ -32,178 +32,178 @@ (function() { -/******************************************************************************/ - -// https://github.com/gorhill/uBlock/issues/2084 -// Anything else than <a>, <b>, <code>, <em>, <i>, <input>, and <span> will -// be rendered as plain text. -// For <input>, only the type attribute is allowed. -// For <a>, only href attribute must be present, and it MUST starts with -// `https://`, and includes no single- or double-quotes. -// No HTML entities are allowed, there is code to handle existing HTML -// entities already present in translation files until they are all gone. - -var reSafeTags = /^([\s\S]*?)<(b|blockquote|code|em|i|kbd|span|sup)>(.+?)<\/\2>([\s\S]*)$/, - reSafeInput = /^([\s\S]*?)<(input type="[^"]+")>(.*?)([\s\S]*)$/, - reInput = /^input type=(['"])([a-z]+)\1$/, - reSafeLink = /^([\s\S]*?)<(a href=['"]https?:\/\/[^'" <>]+['"])>(.+?)<\/a>([\s\S]*)$/, - reLink = /^a href=(['"])(https?:\/\/[^'"]+)\1$/; - -var safeTextToTagNode = function(text) { - var matches, node; - if ( text.lastIndexOf('a ', 0) === 0 ) { - matches = reLink.exec(text); - if ( matches === null ) { return null; } - node = document.createElement('a'); - node.setAttribute('href', matches[2]); - return node; - } - if ( text.lastIndexOf('input ', 0) === 0 ) { - matches = reInput.exec(text); - if ( matches === null ) { return null; } - node = document.createElement('input'); - node.setAttribute('type', matches[2]); - return node; - } - // Firefox extension validator warns if using a variable as argument for - // document.createElement(). - switch ( text ) { - case 'b': - return document.createElement('b'); - case 'blockquote': - return document.createElement('blockquote'); - case 'code': - return document.createElement('code'); - case 'em': - return document.createElement('em'); - case 'i': - return document.createElement('i'); - case 'kbd': - return document.createElement('kbd'); - case 'span': - return document.createElement('span'); - case 'sup': - return document.createElement('sup'); - default: - break; - } -}; - -var safeTextToTextNode = function(text) { - // TODO: remove once no more HTML entities in translation files. - if ( text.indexOf('&') !== -1 ) { - text = text.replace(/“/g, '“') - .replace(/”/g, '”') - .replace(/‘/g, '‘') - .replace(/’/g, '’'); - } - return document.createTextNode(text); -}; - -var safeTextToDOM = function(text, parent) { - if ( text === '' ) { return; } - // Fast path (most common). - if ( text.indexOf('<') === -1 ) { - return parent.appendChild(safeTextToTextNode(text)); - } - // Slow path. - // `<p>` no longer allowed. Code below can be remove once all <p>'s are - // gone from translation files. - text = text.replace(/^<p>|<\/p>/g, '') - .replace(/<p>/g, '\n\n'); - // Parse allowed HTML tags. - var matches, - matches1 = reSafeTags.exec(text), - matches2 = reSafeLink.exec(text); - if ( matches1 !== null && matches2 !== null ) { - matches = matches1.index < matches2.index ? matches1 : matches2; - } else if ( matches1 !== null ) { - matches = matches1; - } else if ( matches2 !== null ) { - matches = matches2; - } else { - matches = reSafeInput.exec(text); - } - if ( matches === null ) { - parent.appendChild(safeTextToTextNode(text)); - return; - } - safeTextToDOM(matches[1], parent); - var node = safeTextToTagNode(matches[2]) || parent; - safeTextToDOM(matches[3], node); - parent.appendChild(node); - safeTextToDOM(matches[4], parent); -}; - -/******************************************************************************/ - -// Helper to deal with the i18n'ing of HTML files. -vAPI.i18n.render = function(context) { - var docu = document, - root = context || docu, - elems, n, i, elem, text; - - elems = root.querySelectorAll('[data-i18n]'); - n = elems.length; - for ( i = 0; i < n; i++ ) { - elem = elems[i]; - text = vAPI.i18n(elem.getAttribute('data-i18n')); - if ( !text ) { continue; } - // TODO: remove once it's all replaced with <input type="..."> - if ( text.indexOf('{') !== -1 ) { - text = text.replace(/\{\{input:([^}]+)\}\}/g, '<input type="$1">'); + /******************************************************************************/ + + // https://github.com/gorhill/uBlock/issues/2084 + // Anything else than <a>, <b>, <code>, <em>, <i>, <input>, and <span> will + // be rendered as plain text. + // For <input>, only the type attribute is allowed. + // For <a>, only href attribute must be present, and it MUST starts with + // `https://`, and includes no single- or double-quotes. + // No HTML entities are allowed, there is code to handle existing HTML + // entities already present in translation files until they are all gone. + + var reSafeTags = /^([\s\S]*?)<(b|blockquote|code|em|i|kbd|span|sup)>(.+?)<\/\2>([\s\S]*)$/, + reSafeInput = /^([\s\S]*?)<(input type="[^"]+")>(.*?)([\s\S]*)$/, + reInput = /^input type=(['"])([a-z]+)\1$/, + reSafeLink = /^([\s\S]*?)<(a href=['"]https?:\/\/[^'" <>]+['"])>(.+?)<\/a>([\s\S]*)$/, + reLink = /^a href=(['"])(https?:\/\/[^'"]+)\1$/; + + var safeTextToTagNode = function(text) { + var matches, node; + if ( text.lastIndexOf('a ', 0) === 0 ) { + matches = reLink.exec(text); + if ( matches === null ) { return null; } + node = document.createElement('a'); + node.setAttribute('href', matches[2]); + return node; } - safeTextToDOM(text, elem); - } - - uDom('[title]', context).forEach(function(elem) { - var title = vAPI.i18n(elem.attr('title')); - if ( title ) { - elem.attr('title', title); + if ( text.lastIndexOf('input ', 0) === 0 ) { + matches = reInput.exec(text); + if ( matches === null ) { return null; } + node = document.createElement('input'); + node.setAttribute('type', matches[2]); + return node; + } + // Firefox extension validator warns if using a variable as argument for + // document.createElement(). + switch ( text ) { + case 'b': + return document.createElement('b'); + case 'blockquote': + return document.createElement('blockquote'); + case 'code': + return document.createElement('code'); + case 'em': + return document.createElement('em'); + case 'i': + return document.createElement('i'); + case 'kbd': + return document.createElement('kbd'); + case 'span': + return document.createElement('span'); + case 'sup': + return document.createElement('sup'); + default: + break; + } + }; + + var safeTextToTextNode = function(text) { + // TODO: remove once no more HTML entities in translation files. + if ( text.indexOf('&') !== -1 ) { + text = text.replace(/“/g, '“') + .replace(/”/g, '”') + .replace(/‘/g, '‘') + .replace(/’/g, '’'); + } + return document.createTextNode(text); + }; + + var safeTextToDOM = function(text, parent) { + if ( text === '' ) { return; } + // Fast path (most common). + if ( text.indexOf('<') === -1 ) { + return parent.appendChild(safeTextToTextNode(text)); + } + // Slow path. + // `<p>` no longer allowed. Code below can be remove once all <p>'s are + // gone from translation files. + text = text.replace(/^<p>|<\/p>/g, '') + .replace(/<p>/g, '\n\n'); + // Parse allowed HTML tags. + var matches, + matches1 = reSafeTags.exec(text), + matches2 = reSafeLink.exec(text); + if ( matches1 !== null && matches2 !== null ) { + matches = matches1.index < matches2.index ? matches1 : matches2; + } else if ( matches1 !== null ) { + matches = matches1; + } else if ( matches2 !== null ) { + matches = matches2; + } else { + matches = reSafeInput.exec(text); + } + if ( matches === null ) { + parent.appendChild(safeTextToTextNode(text)); + return; + } + safeTextToDOM(matches[1], parent); + var node = safeTextToTagNode(matches[2]) || parent; + safeTextToDOM(matches[3], node); + parent.appendChild(node); + safeTextToDOM(matches[4], parent); + }; + + /******************************************************************************/ + + // Helper to deal with the i18n'ing of HTML files. + vAPI.i18n.render = function(context) { + var docu = document, + root = context || docu, + elems, n, i, elem, text; + + elems = root.querySelectorAll('[data-i18n]'); + n = elems.length; + for ( i = 0; i < n; i++ ) { + elem = elems[i]; + text = vAPI.i18n(elem.getAttribute('data-i18n')); + if ( !text ) { continue; } + // TODO: remove once it's all replaced with <input type="..."> + if ( text.indexOf('{') !== -1 ) { + text = text.replace(/\{\{input:([^}]+)\}\}/g, '<input type="$1">'); + } + safeTextToDOM(text, elem); } - }); - - uDom('[placeholder]', context).forEach(function(elem) { - elem.attr('placeholder', vAPI.i18n(elem.attr('placeholder'))); - }); - - uDom('[data-i18n-tip]', context).forEach(function(elem) { - elem.attr( - 'data-tip', - vAPI.i18n(elem.attr('data-i18n-tip')) - .replace(/<br>/g, '\n') - .replace(/\n{3,}/g, '\n\n') - ); - }); -}; - -vAPI.i18n.render(); - -/******************************************************************************/ -vAPI.i18n.renderElapsedTimeToString = function(tstamp) { - var value = (Date.now() - tstamp) / 60000; - if ( value < 2 ) { - return vAPI.i18n('elapsedOneMinuteAgo'); - } - if ( value < 60 ) { - return vAPI.i18n('elapsedManyMinutesAgo').replace('{{value}}', Math.floor(value).toLocaleString()); - } - value /= 60; - if ( value < 2 ) { - return vAPI.i18n('elapsedOneHourAgo'); - } - if ( value < 24 ) { - return vAPI.i18n('elapsedManyHoursAgo').replace('{{value}}', Math.floor(value).toLocaleString()); - } - value /= 24; - if ( value < 2 ) { - return vAPI.i18n('elapsedOneDayAgo'); - } - return vAPI.i18n('elapsedManyDaysAgo').replace('{{value}}', Math.floor(value).toLocaleString()); -}; + uDom('[title]', context).forEach(function(elem) { + var title = vAPI.i18n(elem.attr('title')); + if ( title ) { + elem.attr('title', title); + } + }); + + uDom('[placeholder]', context).forEach(function(elem) { + elem.attr('placeholder', vAPI.i18n(elem.attr('placeholder'))); + }); + + uDom('[data-i18n-tip]', context).forEach(function(elem) { + elem.attr( + 'data-tip', + vAPI.i18n(elem.attr('data-i18n-tip')) + .replace(/<br>/g, '\n') + .replace(/\n{3,}/g, '\n\n') + ); + }); + }; + + vAPI.i18n.render(); + + /******************************************************************************/ + + vAPI.i18n.renderElapsedTimeToString = function(tstamp) { + var value = (Date.now() - tstamp) / 60000; + if ( value < 2 ) { + return vAPI.i18n('elapsedOneMinuteAgo'); + } + if ( value < 60 ) { + return vAPI.i18n('elapsedManyMinutesAgo').replace('{{value}}', Math.floor(value).toLocaleString()); + } + value /= 60; + if ( value < 2 ) { + return vAPI.i18n('elapsedOneHourAgo'); + } + if ( value < 24 ) { + return vAPI.i18n('elapsedManyHoursAgo').replace('{{value}}', Math.floor(value).toLocaleString()); + } + value /= 24; + if ( value < 2 ) { + return vAPI.i18n('elapsedOneDayAgo'); + } + return vAPI.i18n('elapsedManyDaysAgo').replace('{{value}}', Math.floor(value).toLocaleString()); + }; -/******************************************************************************/ + /******************************************************************************/ })(); diff --git a/js/liquid-dict.js b/js/liquid-dict.js index 8e38a75..3284aed 100644 --- a/js/liquid-dict.js +++ b/js/liquid-dict.js @@ -27,176 +27,176 @@ ηMatrix.LiquidDict = (function() { -/******************************************************************************/ - -var LiquidDict = function() { - this.dict = {}; - this.count = 0; - this.duplicateCount = 0; - this.bucketCount = 0; - this.frozenBucketCount = 0; - - // Somewhat arbitrary: I need to come up with hard data to know at which - // point binary search is better than indexOf. - this.cutoff = 500; -}; - -/******************************************************************************/ - -var meltBucket = function(ldict, len, bucket) { - ldict.frozenBucketCount -= 1; - var map = {}; - if ( bucket.charAt(0) === ' ' ) { - bucket.trim().split(' ').map(function(k) { - map[k] = true; - }); - } else { - var offset = 0; - while ( offset < bucket.length ) { - map[bucket.substring(offset, len)] = true; - offset += len; + /******************************************************************************/ + + var LiquidDict = function() { + this.dict = {}; + this.count = 0; + this.duplicateCount = 0; + this.bucketCount = 0; + this.frozenBucketCount = 0; + + // Somewhat arbitrary: I need to come up with hard data to know at which + // point binary search is better than indexOf. + this.cutoff = 500; + }; + + /******************************************************************************/ + + var meltBucket = function(ldict, len, bucket) { + ldict.frozenBucketCount -= 1; + var map = {}; + if ( bucket.charAt(0) === ' ' ) { + bucket.trim().split(' ').map(function(k) { + map[k] = true; + }); + } else { + var offset = 0; + while ( offset < bucket.length ) { + map[bucket.substring(offset, len)] = true; + offset += len; + } } - } - return map; -}; - -/******************************************************************************/ - -var melt = function(ldict) { - var buckets = ldict.dict; - var bucket; - for ( var key in buckets ) { - bucket = buckets[key]; - if ( typeof bucket === 'string' ) { - buckets[key] = meltBucket(ldict, key.charCodeAt(0) & 0xFF, bucket); + return map; + }; + + /******************************************************************************/ + + var melt = function(ldict) { + var buckets = ldict.dict; + var bucket; + for ( var key in buckets ) { + bucket = buckets[key]; + if ( typeof bucket === 'string' ) { + buckets[key] = meltBucket(ldict, key.charCodeAt(0) & 0xFF, bucket); + } } - } -}; + }; -/******************************************************************************/ - -var freezeBucket = function(ldict, bucket) { - ldict.frozenBucketCount += 1; - var words = Object.keys(bucket); - var wordLen = words[0].length; - if ( wordLen * words.length < ldict.cutoff ) { - return ' ' + words.join(' ') + ' '; - } - return words.sort().join(''); -}; + /******************************************************************************/ -/******************************************************************************/ - -// How the key is derived dictates the number and size of buckets. -// -// http://jsperf.com/makekey-concat-vs-join/3 -// -// Question: Why is using a prototyped function better than a standalone -// helper function? - -LiquidDict.prototype.makeKey = function(word) { - var len = word.length; - if ( len > 255 ) { - len = 255; - } - var i = len >> 2; - return String.fromCharCode( - (word.charCodeAt( 0) & 0x03) << 14 | - (word.charCodeAt( i) & 0x03) << 12 | - (word.charCodeAt( i+i) & 0x03) << 10 | - (word.charCodeAt(i+i+i) & 0x03) << 8 | - len - ); -}; + var freezeBucket = function(ldict, bucket) { + ldict.frozenBucketCount += 1; + var words = Object.keys(bucket); + var wordLen = words[0].length; + if ( wordLen * words.length < ldict.cutoff ) { + return ' ' + words.join(' ') + ' '; + } + return words.sort().join(''); + }; + + /******************************************************************************/ + + // How the key is derived dictates the number and size of buckets. + // + // http://jsperf.com/makekey-concat-vs-join/3 + // + // Question: Why is using a prototyped function better than a standalone + // helper function? + + LiquidDict.prototype.makeKey = function(word) { + var len = word.length; + if ( len > 255 ) { + len = 255; + } + var i = len >> 2; + return String.fromCharCode( + (word.charCodeAt( 0) & 0x03) << 14 | + (word.charCodeAt( i) & 0x03) << 12 | + (word.charCodeAt( i+i) & 0x03) << 10 | + (word.charCodeAt(i+i+i) & 0x03) << 8 | + len + ); + }; + + /******************************************************************************/ + + LiquidDict.prototype.test = function(word) { + var key = this.makeKey(word); + var bucket = this.dict[key]; + if ( bucket === undefined ) { + return false; + } + if ( typeof bucket === 'object' ) { + return bucket[word] !== undefined; + } + if ( bucket.charAt(0) === ' ' ) { + return bucket.indexOf(' ' + word + ' ') >= 0; + } + // binary search + var len = word.length; + var left = 0; + // http://jsperf.com/or-vs-floor/3 + var right = ~~(bucket.length / len + 0.5); + var i, needle; + while ( left < right ) { + i = left + right >> 1; + needle = bucket.substr( len * i, len ); + if ( word < needle ) { + right = i; + } else if ( word > needle ) { + left = i + 1; + } else { + return true; + } + } + return false; + }; -/******************************************************************************/ + /******************************************************************************/ -LiquidDict.prototype.test = function(word) { - var key = this.makeKey(word); - var bucket = this.dict[key]; - if ( bucket === undefined ) { - return false; - } - if ( typeof bucket === 'object' ) { - return bucket[word] !== undefined; - } - if ( bucket.charAt(0) === ' ' ) { - return bucket.indexOf(' ' + word + ' ') >= 0; - } - // binary search - var len = word.length; - var left = 0; - // http://jsperf.com/or-vs-floor/3 - var right = ~~(bucket.length / len + 0.5); - var i, needle; - while ( left < right ) { - i = left + right >> 1; - needle = bucket.substr( len * i, len ); - if ( word < needle ) { - right = i; - } else if ( word > needle ) { - left = i + 1; - } else { + LiquidDict.prototype.add = function(word) { + var key = this.makeKey(word); + if ( key === undefined ) { + return false; + } + var bucket = this.dict[key]; + if ( bucket === undefined ) { + this.dict[key] = bucket = {}; + this.bucketCount += 1; + bucket[word] = true; + this.count += 1; return true; + } else if ( typeof bucket === 'string' ) { + this.dict[key] = bucket = meltBucket(this, word.len, bucket); } - } - return false; -}; - -/******************************************************************************/ - -LiquidDict.prototype.add = function(word) { - var key = this.makeKey(word); - if ( key === undefined ) { + if ( bucket[word] === undefined ) { + bucket[word] = true; + this.count += 1; + return true; + } + this.duplicateCount += 1; return false; - } - var bucket = this.dict[key]; - if ( bucket === undefined ) { - this.dict[key] = bucket = {}; - this.bucketCount += 1; - bucket[word] = true; - this.count += 1; - return true; - } else if ( typeof bucket === 'string' ) { - this.dict[key] = bucket = meltBucket(this, word.len, bucket); - } - if ( bucket[word] === undefined ) { - bucket[word] = true; - this.count += 1; - return true; - } - this.duplicateCount += 1; - return false; -}; - -/******************************************************************************/ - -LiquidDict.prototype.freeze = function() { - var buckets = this.dict; - var bucket; - for ( var key in buckets ) { - bucket = buckets[key]; - if ( typeof bucket === 'object' ) { - buckets[key] = freezeBucket(this, bucket); + }; + + /******************************************************************************/ + + LiquidDict.prototype.freeze = function() { + var buckets = this.dict; + var bucket; + for ( var key in buckets ) { + bucket = buckets[key]; + if ( typeof bucket === 'object' ) { + buckets[key] = freezeBucket(this, bucket); + } } - } -}; + }; -/******************************************************************************/ + /******************************************************************************/ -LiquidDict.prototype.reset = function() { - this.dict = {}; - this.count = 0; - this.duplicateCount = 0; - this.bucketCount = 0; - this.frozenBucketCount = 0; -}; + LiquidDict.prototype.reset = function() { + this.dict = {}; + this.count = 0; + this.duplicateCount = 0; + this.bucketCount = 0; + this.frozenBucketCount = 0; + }; -/******************************************************************************/ + /******************************************************************************/ -return LiquidDict; + return LiquidDict; -/******************************************************************************/ + /******************************************************************************/ })(); diff --git a/js/logger-ui.js b/js/logger-ui.js index ed2c313..cb8e4f1 100644 --- a/js/logger-ui.js +++ b/js/logger-ui.js @@ -27,883 +27,900 @@ /******************************************************************************/ -(function() { +(function () { -/******************************************************************************/ + /******************************************************************************/ -var tbody = document.querySelector('#content tbody'); -var trJunkyard = []; -var tdJunkyard = []; -var firstVarDataCol = 2; // currently, column 2 (0-based index) -var lastVarDataIndex = 3; // currently, d0-d3 -var maxEntries = 0; -var noTabId = ''; -var allTabIds = {}; -var allTabIdsToken; -var ownerId = Date.now(); - -var emphasizeTemplate = document.querySelector('#emphasizeTemplate > span'); -var hiddenTemplate = document.querySelector('#hiddenTemplate > span'); - -var prettyRequestTypes = { - 'main_frame': 'doc', - 'stylesheet': 'css', - 'sub_frame': 'frame', - 'xmlhttprequest': 'xhr' -}; - -var dontEmphasizeSet = new Set([ - 'COOKIE', - 'CSP', - 'REFERER' -]); + var tbody = document.querySelector('#content tbody'); + var trJunkyard = []; + var tdJunkyard = []; + var firstVarDataCol = 2; // currently, column 2 (0-based index) + var lastVarDataIndex = 3; // currently, d0-d3 + var maxEntries = 0; + var noTabId = ''; + var allTabIds = {}; + var allTabIdsToken; + var ownerId = Date.now(); + + var emphasizeTemplate = document.querySelector('#emphasizeTemplate > span'); + var hiddenTemplate = document.querySelector('#hiddenTemplate > span'); + + var prettyRequestTypes = { + 'main_frame': 'doc', + 'stylesheet': 'css', + 'sub_frame': 'frame', + 'xmlhttprequest': 'xhr' + }; -/******************************************************************************/ + var dontEmphasizeSet = new Set([ + 'COOKIE', + 'CSP', + 'REFERER' + ]); -// Adjust top padding of content table, to match that of toolbar height. + /******************************************************************************/ -document.getElementById('content').style.setProperty( - 'margin-top', - document.getElementById('toolbar').clientHeight + 'px' -); + // Adjust top padding of content table, to match that of toolbar height. -/******************************************************************************/ - -var classNameFromTabId = function(tabId) { - if ( tabId === noTabId ) { - return 'tab_bts'; - } - if ( tabId !== '' ) { - return 'tab_' + tabId; - } - return ''; -}; + document.getElementById('content').style.setProperty( + 'margin-top', + document.getElementById('toolbar').clientHeight + 'px' + ); -/******************************************************************************/ + /******************************************************************************/ -// Emphasize hostname and cookie name. + var classNameFromTabId = function (tabId) { + if (tabId === noTabId) { + return 'tab_bts'; + } + if (tabId !== '') { + return 'tab_' + tabId; + } + return ''; + }; -var emphasizeCookie = function(s) { - var pnode = emphasizeHostname(s); - if ( pnode.childNodes.length !== 3 ) { - return pnode; - } - var prefix = '-cookie:'; - var text = pnode.childNodes[2].textContent; - var beg = text.indexOf(prefix); - if ( beg === -1 ) { - return pnode; - } - beg += prefix.length; - var end = text.indexOf('}', beg); - if ( end === -1 ) { + /******************************************************************************/ + + // Emphasize hostname and cookie name. + + var emphasizeCookie = function (s) { + var pnode = emphasizeHostname(s); + if (pnode.childNodes.length !== 3) { + return pnode; + } + var prefix = '-cookie:'; + var text = pnode.childNodes[2].textContent; + var beg = text.indexOf(prefix); + if (beg === -1) { + return pnode; + } + beg += prefix.length; + var end = text.indexOf('}', beg); + if (end === -1) { + return pnode; + } + var cnode = emphasizeTemplate.cloneNode(true); + cnode.childNodes[0].textContent = text.slice(0, beg); + cnode.childNodes[1].textContent = text.slice(beg, end); + cnode.childNodes[2].textContent = text.slice(end); + pnode.replaceChild(cnode.childNodes[0], pnode.childNodes[2]); + pnode.appendChild(cnode.childNodes[0]); + pnode.appendChild(cnode.childNodes[0]); return pnode; - } - var cnode = emphasizeTemplate.cloneNode(true); - cnode.childNodes[0].textContent = text.slice(0, beg); - cnode.childNodes[1].textContent = text.slice(beg, end); - cnode.childNodes[2].textContent = text.slice(end); - pnode.replaceChild(cnode.childNodes[0], pnode.childNodes[2]); - pnode.appendChild(cnode.childNodes[0]); - pnode.appendChild(cnode.childNodes[0]); - return pnode; -}; + }; -/******************************************************************************/ + /******************************************************************************/ -// Emphasize hostname in URL. + // Emphasize hostname in URL. -var emphasizeHostname = function(url) { - var hnbeg = url.indexOf('://'); - if ( hnbeg === -1 ) { - return document.createTextNode(url); - } - hnbeg += 3; + var emphasizeHostname = function (url) { + var hnbeg = url.indexOf('://'); + if (hnbeg === -1) { + return document.createTextNode(url); + } + hnbeg += 3; - var hnend = url.indexOf('/', hnbeg); - if ( hnend === -1 ) { - hnend = url.slice(hnbeg).search(/\?#/); - if ( hnend !== -1 ) { - hnend += hnbeg; - } else { - hnend = url.length; + var hnend = url.indexOf('/', hnbeg); + if (hnend === -1) { + hnend = url.slice(hnbeg).search(/\?#/); + if (hnend !== -1) { + hnend += hnbeg; + } else { + hnend = url.length; + } } - } - var node = emphasizeTemplate.cloneNode(true); - node.childNodes[0].textContent = url.slice(0, hnbeg); - node.childNodes[1].textContent = url.slice(hnbeg, hnend); - node.childNodes[2].textContent = url.slice(hnend); - return node; -}; + var node = emphasizeTemplate.cloneNode(true); + node.childNodes[0].textContent = url.slice(0, hnbeg); + node.childNodes[1].textContent = url.slice(hnbeg, hnend); + node.childNodes[2].textContent = url.slice(hnend); + return node; + }; -/******************************************************************************/ + /******************************************************************************/ -var createCellAt = function(tr, index) { - var td = tr.cells[index]; - var mustAppend = !td; - if ( mustAppend ) { - td = tdJunkyard.pop(); - } - if ( td ) { - td.removeAttribute('colspan'); - td.textContent = ''; - } else { - td = document.createElement('td'); - } - if ( mustAppend ) { - tr.appendChild(td); - } - return td; -}; + var createCellAt = function (tr, index) { + var td = tr.cells[index]; + var mustAppend = !td; + if (mustAppend) { + td = tdJunkyard.pop(); + } + if (td) { + td.removeAttribute('colspan'); + td.textContent = ''; + } else { + td = document.createElement('td'); + } + if (mustAppend) { + tr.appendChild(td); + } + return td; + }; -/******************************************************************************/ + /******************************************************************************/ -var createRow = function(layout) { - var tr = trJunkyard.pop(); - if ( tr ) { - tr.className = ''; - } else { - tr = document.createElement('tr'); - } - for ( var index = 0; index < firstVarDataCol; index++ ) { - createCellAt(tr, index); - } - var i = 1, span = 1, td; - for (;;) { - td = createCellAt(tr, index); - if ( i === lastVarDataIndex ) { - break; - } - if ( layout.charAt(i) !== '1' ) { - span += 1; + var createRow = function (layout) { + var tr = trJunkyard.pop(); + if (tr) { + tr.className = ''; } else { - if ( span !== 1 ) { - td.setAttribute('colspan', span); - } - index += 1; - span = 1; - } - i += 1; - } - if ( span !== 1 ) { - td.setAttribute('colspan', span); - } - index += 1; - while ( (td = tr.cells[index]) ) { - tdJunkyard.push(tr.removeChild(td)); - } - return tr; -}; - -/******************************************************************************/ + tr = document.createElement('tr'); + } + for (var index = 0; index < firstVarDataCol; index++) { + createCellAt(tr, index); + } + var i = 1, + span = 1, + td; + for (;;) { + td = createCellAt(tr, index); + if (i === lastVarDataIndex) { + break; + } + if (layout.charAt(i) !== '1') { + span += 1; + } else { + if (span !== 1) { + td.setAttribute('colspan', span); + } + index += 1; + span = 1; + } + i += 1; + } + if (span !== 1) { + td.setAttribute('colspan', span); + } + index += 1; + while ((td = tr.cells[index])) { + tdJunkyard.push(tr.removeChild(td)); + } + return tr; + }; -var createHiddenTextNode = function(text) { - var node = hiddenTemplate.cloneNode(true); - node.textContent = text; - return node; -}; + /******************************************************************************/ -/******************************************************************************/ + var createHiddenTextNode = function (text) { + var node = hiddenTemplate.cloneNode(true); + node.textContent = text; + return node; + }; -var padTo2 = function(v) { - return v < 10 ? '0' + v : v; -}; + /******************************************************************************/ -/******************************************************************************/ + var padTo2 = function (v) { + return v < 10 ? '0' + v : v; + }; -var createGap = function(tabId, url) { - var tr = createRow('1'); - tr.classList.add('doc'); - tr.classList.add('tab'); - tr.classList.add('canMtx'); - tr.classList.add('tab_' + tabId); - tr.cells[firstVarDataCol].textContent = url; - tbody.insertBefore(tr, tbody.firstChild); -}; + /******************************************************************************/ -/******************************************************************************/ + var createGap = function (tabId, url) { + var tr = createRow('1'); + tr.classList.add('doc'); + tr.classList.add('tab'); + tr.classList.add('canMtx'); + tr.classList.add('tab_' + tabId); + tr.cells[firstVarDataCol].textContent = url; + tbody.insertBefore(tr, tbody.firstChild); + }; -var renderLogEntry = function(entry) { - var tr; - var fvdc = firstVarDataCol; + /******************************************************************************/ - switch ( entry.cat ) { - case 'error': - case 'info': - tr = createRow('1'); - if ( entry.d0 === 'cookie' ) { - tr.cells[fvdc].appendChild(emphasizeCookie(entry.d1)); - } else { - tr.cells[fvdc].textContent = entry.d0; - } - break; + var renderLogEntry = function (entry) { + var tr; + var fvdc = firstVarDataCol; - case 'net': - tr = createRow('111'); - tr.classList.add('canMtx'); - // If the request is that of a root frame, insert a gap in the table - // in order to visually separate entries for different documents. - if ( entry.d2 === 'doc' && entry.tab !== noTabId ) { - createGap(entry.tab, entry.d1); - } - if ( entry.d3 ) { - tr.classList.add('blocked'); - tr.cells[fvdc].textContent = '--'; - } else { - tr.cells[fvdc].textContent = ''; + switch (entry.cat) { + case 'error': + case 'info': + tr = createRow('1'); + if (entry.d0 === 'cookie') { + tr.cells[fvdc].appendChild(emphasizeCookie(entry.d1)); + } else { + tr.cells[fvdc].textContent = entry.d0; + } + break; + + case 'net': + tr = createRow('111'); + tr.classList.add('canMtx'); + // If the request is that of a root frame, insert a gap in the table + // in order to visually separate entries for different documents. + if (entry.d2 === 'doc' && entry.tab !== noTabId) { + createGap(entry.tab, entry.d1); + } + if (entry.d3) { + tr.classList.add('blocked'); + tr.cells[fvdc].textContent = '--'; + } else { + tr.cells[fvdc].textContent = ''; + } + tr.cells[fvdc + 1].textContent = (prettyRequestTypes[entry.d2] || entry.d2); + if (dontEmphasizeSet.has(entry.d2)) { + tr.cells[fvdc + 2].textContent = entry.d1; + } else if (entry.d2 === 'cookie') { + tr.cells[fvdc + 2].appendChild(emphasizeCookie(entry.d1)); + } else { + tr.cells[fvdc + 2].appendChild(emphasizeHostname(entry.d1)); + } + break; + + default: + tr = createRow('1'); + tr.cells[fvdc].textContent = entry.d0; + break; + } + + // Fields common to all rows. + var time = logDate; + time.setTime(entry.tstamp - logDateTimezoneOffset); + tr.cells[0].textContent = padTo2(time.getUTCHours()) + ':' + + padTo2(time.getUTCMinutes()) + ':' + + padTo2(time.getSeconds()); + + if (entry.tab) { + tr.classList.add('tab'); + tr.classList.add(classNameFromTabId(entry.tab)); + if (entry.tab === noTabId) { + tr.cells[1].appendChild(createHiddenTextNode('bts')); + } } - tr.cells[fvdc+1].textContent = (prettyRequestTypes[entry.d2] || entry.d2); - if ( dontEmphasizeSet.has(entry.d2) ) { - tr.cells[fvdc+2].textContent = entry.d1; - } else if ( entry.d2 === 'cookie' ) { - tr.cells[fvdc+2].appendChild(emphasizeCookie(entry.d1)); - } else { - tr.cells[fvdc+2].appendChild(emphasizeHostname(entry.d1)); + if (entry.cat !== '') { + tr.classList.add('cat_' + entry.cat); } - break; - default: - tr = createRow('1'); - tr.cells[fvdc].textContent = entry.d0; - break; - } + rowFilterer.filterOne(tr, true); - // Fields common to all rows. - var time = logDate; - time.setTime(entry.tstamp - logDateTimezoneOffset); - tr.cells[0].textContent = padTo2(time.getUTCHours()) + ':' + - padTo2(time.getUTCMinutes()) + ':' + - padTo2(time.getSeconds()); + tbody.insertBefore(tr, tbody.firstChild); + }; - if ( entry.tab ) { - tr.classList.add('tab'); - tr.classList.add(classNameFromTabId(entry.tab)); - if ( entry.tab === noTabId ) { - tr.cells[1].appendChild(createHiddenTextNode('bts')); + // Reuse date objects. + var logDate = new Date(), + logDateTimezoneOffset = logDate.getTimezoneOffset() * 60000; + + /******************************************************************************/ + + var renderLogEntries = function (response) { + var entries = response.entries; + if (entries.length === 0) { + return; } - } - if ( entry.cat !== '' ) { - tr.classList.add('cat_' + entry.cat); - } - rowFilterer.filterOne(tr, true); + // Preserve scroll position + var height = tbody.offsetHeight; + + var tabIds = response.tabIds; + var n = entries.length; + var entry; + for (var i = 0; i < n; i++) { + entry = entries[i]; + // Unlikely, but it may happen + if (entry.tab && tabIds.hasOwnProperty(entry.tab) === false) { + continue; + } + renderLogEntry(entries[i]); + } - tbody.insertBefore(tr, tbody.firstChild); -}; + // Prevent logger from growing infinitely and eating all memory. For + // instance someone could forget that it is left opened for some + // dynamically refreshed pages. + truncateLog(maxEntries); -// Reuse date objects. -var logDate = new Date(), - logDateTimezoneOffset = logDate.getTimezoneOffset() * 60000; + var yDelta = tbody.offsetHeight - height; + if (yDelta === 0) { + return; + } -/******************************************************************************/ + // Chromium: + // body.scrollTop = good value + // body.parentNode.scrollTop = 0 + if (document.body.scrollTop !== 0) { + document.body.scrollTop += yDelta; + return; + } -var renderLogEntries = function(response) { - var entries = response.entries; - if ( entries.length === 0 ) { - return; - } - - // Preserve scroll position - var height = tbody.offsetHeight; - - var tabIds = response.tabIds; - var n = entries.length; - var entry; - for ( var i = 0; i < n; i++ ) { - entry = entries[i]; - // Unlikely, but it may happen - if ( entry.tab && tabIds.hasOwnProperty(entry.tab) === false ) { - continue; - } - renderLogEntry(entries[i]); - } - - // Prevent logger from growing infinitely and eating all memory. For - // instance someone could forget that it is left opened for some - // dynamically refreshed pages. - truncateLog(maxEntries); - - var yDelta = tbody.offsetHeight - height; - if ( yDelta === 0 ) { - return; - } - - // Chromium: - // body.scrollTop = good value - // body.parentNode.scrollTop = 0 - if ( document.body.scrollTop !== 0 ) { - document.body.scrollTop += yDelta; - return; - } - - // Firefox: - // body.scrollTop = 0 - // body.parentNode.scrollTop = good value - var parentNode = document.body.parentNode; - if ( parentNode && parentNode.scrollTop !== 0 ) { - parentNode.scrollTop += yDelta; - } -}; + // Firefox: + // body.scrollTop = 0 + // body.parentNode.scrollTop = good value + var parentNode = document.body.parentNode; + if (parentNode && parentNode.scrollTop !== 0) { + parentNode.scrollTop += yDelta; + } + }; -/******************************************************************************/ + /******************************************************************************/ -var synchronizeTabIds = function(newTabIds) { - var oldTabIds = allTabIds; - var autoDeleteVoidRows = !!vAPI.localStorage.getItem('loggerAutoDeleteVoidRows'); - var rowVoided = false; - var trs; - for ( var tabId in oldTabIds ) { - if ( oldTabIds.hasOwnProperty(tabId) === false ) { - continue; - } - if ( newTabIds.hasOwnProperty(tabId) ) { - continue; - } - // Mark or remove voided rows - trs = uDom('.tab_' + tabId); - if ( autoDeleteVoidRows ) { - toJunkyard(trs); - } else { - trs.removeClass('canMtx'); - rowVoided = true; - } - // Remove popup if it is currently bound to a removed tab. - if ( tabId === popupManager.tabId ) { - popupManager.toggleOff(); - } - } - - var select = document.getElementById('pageSelector'); - var selectValue = select.value; - var tabIds = Object.keys(newTabIds).sort(function(a, b) { - return newTabIds[a].localeCompare(newTabIds[b]); - }); - var option; - for ( var i = 0, j = 2; i < tabIds.length; i++ ) { - tabId = tabIds[i]; - if ( tabId === noTabId ) { - continue; - } - option = select.options[j]; - j += 1; - if ( !option ) { - option = document.createElement('option'); - select.appendChild(option); - } - option.textContent = newTabIds[tabId]; - option.value = classNameFromTabId(tabId); - if ( option.value === selectValue ) { - option.setAttribute('selected', ''); - } else { - option.removeAttribute('selected'); + var synchronizeTabIds = function (newTabIds) { + var oldTabIds = allTabIds; + var autoDeleteVoidRows = !!vAPI.localStorage.getItem('loggerAutoDeleteVoidRows'); + var rowVoided = false; + var trs; + for (var tabId in oldTabIds) { + if (oldTabIds.hasOwnProperty(tabId) === false) { + continue; + } + if (newTabIds.hasOwnProperty(tabId)) { + continue; + } + // Mark or remove voided rows + trs = uDom('.tab_' + tabId); + if (autoDeleteVoidRows) { + toJunkyard(trs); + } else { + trs.removeClass('canMtx'); + rowVoided = true; + } + // Remove popup if it is currently bound to a removed tab. + if (tabId === popupManager.tabId) { + popupManager.toggleOff(); + } } - } - while ( j < select.options.length ) { - select.removeChild(select.options[j]); - } - if ( select.value !== selectValue ) { - select.selectedIndex = 0; - select.value = ''; - select.options[0].setAttribute('selected', ''); - pageSelectorChanged(); - } - allTabIds = newTabIds; + var select = document.getElementById('pageSelector'); + var selectValue = select.value; + var tabIds = Object.keys(newTabIds).sort(function (a, b) { + return newTabIds[a].localeCompare(newTabIds[b]); + }); + var option; + for (var i = 0, j = 2; i < tabIds.length; i++) { + tabId = tabIds[i]; + if (tabId === noTabId) { + continue; + } + option = select.options[j]; + j += 1; + if (!option) { + option = document.createElement('option'); + select.appendChild(option); + } + option.textContent = newTabIds[tabId]; + option.value = classNameFromTabId(tabId); + if (option.value === selectValue) { + option.setAttribute('selected', ''); + } else { + option.removeAttribute('selected'); + } + } + while (j < select.options.length) { + select.removeChild(select.options[j]); + } + if (select.value !== selectValue) { + select.selectedIndex = 0; + select.value = ''; + select.options[0].setAttribute('selected', ''); + pageSelectorChanged(); + } - return rowVoided; -}; + allTabIds = newTabIds; -/******************************************************************************/ + return rowVoided; + }; -var truncateLog = function(size) { - if ( size === 0 ) { - size = 5000; - } - var tbody = document.querySelector('#content tbody'); - size = Math.min(size, 10000); - var tr; - while ( tbody.childElementCount > size ) { - tr = tbody.lastElementChild; - trJunkyard.push(tbody.removeChild(tr)); - } -}; + /******************************************************************************/ -/******************************************************************************/ + var truncateLog = function (size) { + if (size === 0) { + size = 5000; + } + var tbody = document.querySelector('#content tbody'); + size = Math.min(size, 10000); + var tr; + while (tbody.childElementCount > size) { + tr = tbody.lastElementChild; + trJunkyard.push(tbody.removeChild(tr)); + } + }; -var onLogBufferRead = function(response) { - if ( !response || response.unavailable ) { - readLogBufferAsync(); - return; - } + /******************************************************************************/ - // This tells us the behind-the-scene tab id - noTabId = response.noTabId; + var onLogBufferRead = function (response) { + if (!response || response.unavailable) { + readLogBufferAsync(); + return; + } - // This may have changed meanwhile - if ( response.maxLoggedRequests !== maxEntries ) { - maxEntries = response.maxLoggedRequests; - uDom('#maxEntries').val(maxEntries || ''); - } + // This tells us the behind-the-scene tab id + noTabId = response.noTabId; - // Neuter rows for which a tab does not exist anymore - var rowVoided = false; - if ( response.tabIdsToken !== allTabIdsToken ) { - rowVoided = synchronizeTabIds(response.tabIds); - allTabIdsToken = response.tabIdsToken; - } + // This may have changed meanwhile + if (response.maxLoggedRequests !== maxEntries) { + maxEntries = response.maxLoggedRequests; + uDom('#maxEntries').val(maxEntries || ''); + } - renderLogEntries(response); + // Neuter rows for which a tab does not exist anymore + var rowVoided = false; + if (response.tabIdsToken !== allTabIdsToken) { + rowVoided = synchronizeTabIds(response.tabIds); + allTabIdsToken = response.tabIdsToken; + } + + renderLogEntries(response); + + if (rowVoided) { + uDom('#clean').toggleClass( + 'disabled', + tbody.querySelector('tr.tab:not(.canMtx)') === null + ); + } - if ( rowVoided ) { - uDom('#clean').toggleClass( + // Synchronize toolbar with content of log + uDom('#clear').toggleClass( 'disabled', - tbody.querySelector('tr.tab:not(.canMtx)') === null + tbody.querySelector('tr') === null ); - } - // Synchronize toolbar with content of log - uDom('#clear').toggleClass( - 'disabled', - tbody.querySelector('tr') === null - ); - - readLogBufferAsync(); -}; + readLogBufferAsync(); + }; -/******************************************************************************/ + /******************************************************************************/ -// This can be called only once, at init time. After that, this will be called -// automatically. If called after init time, this will be messy, and this would -// require a bit more code to ensure no multi time out events. + // This can be called only once, at init time. After that, this will be called + // automatically. If called after init time, this will be messy, and this would + // require a bit more code to ensure no multi time out events. -var readLogBuffer = function() { - if ( ownerId === undefined ) { return; } - vAPI.messaging.send( - 'logger-ui.js', - { what: 'readMany', ownerId: ownerId }, - onLogBufferRead - ); -}; + var readLogBuffer = function () { + if (ownerId === undefined) { + return; + } + vAPI.messaging.send( + 'logger-ui.js', { + what: 'readMany', + ownerId: ownerId + }, + onLogBufferRead + ); + }; -var readLogBufferAsync = function() { - if ( ownerId === undefined ) { return; } - vAPI.setTimeout(readLogBuffer, 1200); -}; + var readLogBufferAsync = function () { + if (ownerId === undefined) { + return; + } + vAPI.setTimeout(readLogBuffer, 1200); + }; -/******************************************************************************/ + /******************************************************************************/ -var pageSelectorChanged = function() { - var style = document.getElementById('tabFilterer'); - var tabClass = document.getElementById('pageSelector').value; - var sheet = style.sheet; - while ( sheet.cssRules.length !== 0 ) { - sheet.deleteRule(0); - } - if ( tabClass !== '' ) { - sheet.insertRule( - '#content table tr:not(.' + tabClass + ') { display: none; }', - 0 + var pageSelectorChanged = function () { + var style = document.getElementById('tabFilterer'); + var tabClass = document.getElementById('pageSelector').value; + var sheet = style.sheet; + while (sheet.cssRules.length !== 0) { + sheet.deleteRule(0); + } + if (tabClass !== '') { + sheet.insertRule( + '#content table tr:not(.' + tabClass + ') { display: none; }', + 0 + ); + } + uDom('#refresh').toggleClass( + 'disabled', + tabClass === '' || tabClass === 'tab_bts' ); - } - uDom('#refresh').toggleClass( - 'disabled', - tabClass === '' || tabClass === 'tab_bts' - ); -}; + }; -/******************************************************************************/ + /******************************************************************************/ -var refreshTab = function() { - var tabClass = document.getElementById('pageSelector').value; - var matches = tabClass.match(/^tab_(.+)$/); - if ( matches === null ) { - return; - } - if ( matches[1] === 'bts' ) { - return; - } - vAPI.messaging.send( - 'logger-ui.js', - { what: 'forceReloadTab', tabId: matches[1] } - ); -}; + var refreshTab = function () { + var tabClass = document.getElementById('pageSelector').value; + var matches = tabClass.match(/^tab_(.+)$/); + if (matches === null) { + return; + } + if (matches[1] === 'bts') { + return; + } + vAPI.messaging.send( + 'logger-ui.js', { + what: 'forceReloadTab', + tabId: matches[1] + } + ); + }; -/******************************************************************************/ + /******************************************************************************/ -var onMaxEntriesChanged = function() { - var raw = uDom(this).val(); - try { - maxEntries = parseInt(raw, 10); - if ( isNaN(maxEntries) ) { + var onMaxEntriesChanged = function () { + var raw = uDom(this).val(); + try { + maxEntries = parseInt(raw, 10); + if (isNaN(maxEntries)) { + maxEntries = 0; + } + } catch (e) { maxEntries = 0; } - } catch (e) { - maxEntries = 0; - } - - vAPI.messaging.send('logger-ui.js', { - what: 'userSettings', - name: 'maxLoggedRequests', - value: maxEntries - }); - truncateLog(maxEntries); -}; + vAPI.messaging.send('logger-ui.js', { + what: 'userSettings', + name: 'maxLoggedRequests', + value: maxEntries + }); -/******************************************************************************/ + truncateLog(maxEntries); + }; -var rowFilterer = (function() { - var filters = []; - - var parseInput = function() { - filters = []; - - var rawPart, hardBeg, hardEnd; - var raw = uDom('#filterInput').val().trim(); - var rawParts = raw.split(/\s+/); - var reStr, reStrs = [], not = false; - var n = rawParts.length; - for ( var i = 0; i < n; i++ ) { - rawPart = rawParts[i]; - if ( rawPart.charAt(0) === '!' ) { - if ( reStrs.length === 0 ) { - not = true; + /******************************************************************************/ + + var rowFilterer = (function () { + var filters = []; + + var parseInput = function () { + filters = []; + + var rawPart, hardBeg, hardEnd; + var raw = uDom('#filterInput').val().trim(); + var rawParts = raw.split(/\s+/); + var reStr, reStrs = [], + not = false; + var n = rawParts.length; + for (var i = 0; i < n; i++) { + rawPart = rawParts[i]; + if (rawPart.charAt(0) === '!') { + if (reStrs.length === 0) { + not = true; + } + rawPart = rawPart.slice(1); } - rawPart = rawPart.slice(1); - } - hardBeg = rawPart.charAt(0) === '|'; - if ( hardBeg ) { - rawPart = rawPart.slice(1); - } - hardEnd = rawPart.slice(-1) === '|'; - if ( hardEnd ) { - rawPart = rawPart.slice(0, -1); - } - if ( rawPart === '' ) { - continue; + hardBeg = rawPart.charAt(0) === '|'; + if (hardBeg) { + rawPart = rawPart.slice(1); + } + hardEnd = rawPart.slice(-1) === '|'; + if (hardEnd) { + rawPart = rawPart.slice(0, -1); + } + if (rawPart === '') { + continue; + } + // https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions + reStr = rawPart.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + if (hardBeg) { + reStr = '(?:^|\\s)' + reStr; + } + if (hardEnd) { + reStr += '(?:\\s|$)'; + } + reStrs.push(reStr); + if (i < (n - 1) && rawParts[i + 1] === '||') { + i += 1; + continue; + } + reStr = reStrs.length === 1 ? reStrs[0] : reStrs.join('|'); + filters.push({ + re: new RegExp(reStr, 'i'), + r: !not + }); + reStrs = []; + not = false; } - // https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions - reStr = rawPart.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - if ( hardBeg ) { - reStr = '(?:^|\\s)' + reStr; + }; + + var filterOne = function (tr, clean) { + var ff = filters; + var fcount = ff.length; + if (fcount === 0 && clean === true) { + return; } - if ( hardEnd ) { - reStr += '(?:\\s|$)'; + // do not filter out doc boundaries, they help separate important + // section of log. + var cl = tr.classList; + if (cl.contains('doc')) { + return; } - reStrs.push(reStr); - if ( i < (n - 1) && rawParts[i + 1] === '||' ) { - i += 1; - continue; + if (fcount === 0) { + cl.remove('f'); + return; } - reStr = reStrs.length === 1 ? reStrs[0] : reStrs.join('|'); - filters.push({ - re: new RegExp(reStr, 'i'), - r: !not - }); - reStrs = []; - not = false; - } - }; - - var filterOne = function(tr, clean) { - var ff = filters; - var fcount = ff.length; - if ( fcount === 0 && clean === true ) { - return; - } - // do not filter out doc boundaries, they help separate important - // section of log. - var cl = tr.classList; - if ( cl.contains('doc') ) { - return; - } - if ( fcount === 0 ) { - cl.remove('f'); - return; - } - var cc = tr.cells; - var ccount = cc.length; - var hit, j, f; - // each filter expression must hit (implicit and-op) - // if... - // positive filter expression = there must one hit on any field - // negative filter expression = there must be no hit on all fields - for ( var i = 0; i < fcount; i++ ) { - f = ff[i]; - hit = !f.r; - for ( j = 0; j < ccount; j++ ) { - if ( f.re.test(cc[j].textContent) ) { - hit = f.r; - break; + var cc = tr.cells; + var ccount = cc.length; + var hit, j, f; + // each filter expression must hit (implicit and-op) + // if... + // positive filter expression = there must one hit on any field + // negative filter expression = there must be no hit on all fields + for (var i = 0; i < fcount; i++) { + f = ff[i]; + hit = !f.r; + for (j = 0; j < ccount; j++) { + if (f.re.test(cc[j].textContent)) { + hit = f.r; + break; + } + } + if (!hit) { + cl.add('f'); + return; } } - if ( !hit ) { - cl.add('f'); + cl.remove('f'); + }; + + var filterAll = function () { + // Special case: no filter + if (filters.length === 0) { + uDom('#content tr').removeClass('f'); return; } - } - cl.remove('f'); - }; - - var filterAll = function() { - // Special case: no filter - if ( filters.length === 0 ) { - uDom('#content tr').removeClass('f'); - return; - } - var tbody = document.querySelector('#content tbody'); - var rows = tbody.rows; - var i = rows.length; - while ( i-- ) { - filterOne(rows[i]); - } - }; - - var onFilterChangedAsync = (function() { - var timer = null; - var commit = function() { - timer = null; - parseInput(); - filterAll(); - }; - return function() { - if ( timer !== null ) { - clearTimeout(timer); + var tbody = document.querySelector('#content tbody'); + var rows = tbody.rows; + var i = rows.length; + while (i--) { + filterOne(rows[i]); } - timer = vAPI.setTimeout(commit, 750); }; - })(); - var onFilterButton = function() { - var cl = document.body.classList; - cl.toggle('f', cl.contains('f') === false); - }; + var onFilterChangedAsync = (function () { + var timer = null; + var commit = function () { + timer = null; + parseInput(); + filterAll(); + }; + return function () { + if (timer !== null) { + clearTimeout(timer); + } + timer = vAPI.setTimeout(commit, 750); + }; + })(); - uDom('#filterButton').on('click', onFilterButton); - uDom('#filterInput').on('input', onFilterChangedAsync); + var onFilterButton = function () { + var cl = document.body.classList; + cl.toggle('f', cl.contains('f') === false); + }; - return { - filterOne: filterOne, - filterAll: filterAll - }; -})(); + uDom('#filterButton').on('click', onFilterButton); + uDom('#filterInput').on('input', onFilterChangedAsync); -/******************************************************************************/ + return { + filterOne: filterOne, + filterAll: filterAll + }; + })(); -var toJunkyard = function(trs) { - trs.remove(); - var i = trs.length; - while ( i-- ) { - trJunkyard.push(trs.nodeAt(i)); - } -}; + /******************************************************************************/ -/******************************************************************************/ + var toJunkyard = function (trs) { + trs.remove(); + var i = trs.length; + while (i--) { + trJunkyard.push(trs.nodeAt(i)); + } + }; -var clearBuffer = function() { - var tbody = document.querySelector('#content tbody'); - var tr; - while ( tbody.firstChild !== null ) { - tr = tbody.lastElementChild; - trJunkyard.push(tbody.removeChild(tr)); - } - uDom('#clear').addClass('disabled'); - uDom('#clean').addClass('disabled'); -}; + /******************************************************************************/ -/******************************************************************************/ + var clearBuffer = function () { + var tbody = document.querySelector('#content tbody'); + var tr; + while (tbody.firstChild !== null) { + tr = tbody.lastElementChild; + trJunkyard.push(tbody.removeChild(tr)); + } + uDom('#clear').addClass('disabled'); + uDom('#clean').addClass('disabled'); + }; -var cleanBuffer = function() { - var rows = uDom('#content tr.tab:not(.canMtx)').remove(); - var i = rows.length; - while ( i-- ) { - trJunkyard.push(rows.nodeAt(i)); - } - uDom('#clean').addClass('disabled'); -}; + /******************************************************************************/ -/******************************************************************************/ + var cleanBuffer = function () { + var rows = uDom('#content tr.tab:not(.canMtx)').remove(); + var i = rows.length; + while (i--) { + trJunkyard.push(rows.nodeAt(i)); + } + uDom('#clean').addClass('disabled'); + }; -var toggleCompactView = function() { - document.body.classList.toggle('compactView'); - uDom('#content table .vExpanded').removeClass('vExpanded'); -}; + /******************************************************************************/ -var toggleCompactRow = function(ev) { - ev.target.parentElement.classList.toggle('vExpanded'); -}; + var toggleCompactView = function () { + document.body.classList.toggle('compactView'); + uDom('#content table .vExpanded').removeClass('vExpanded'); + }; -/******************************************************************************/ + var toggleCompactRow = function (ev) { + ev.target.parentElement.classList.toggle('vExpanded'); + }; -var popupManager = (function() { - var realTabId = null; - var localTabId = null; - var container = null; - var popup = null; - var popupObserver = null; - var style = null; - var styleTemplate = [ - 'tr:not(.tab_{{tabId}}) {', + /******************************************************************************/ + + var popupManager = (function () { + var realTabId = null; + var localTabId = null; + var container = null; + var popup = null; + var popupObserver = null; + var style = null; + var styleTemplate = [ + 'tr:not(.tab_{{tabId}}) {', 'cursor: not-allowed;', 'opacity: 0.2;', - '}' - ].join('\n'); + '}' + ].join('\n'); - var resizePopup = function() { - if ( popup === null ) { - return; - } - var popupBody = popup.contentWindow.document.body; - if ( popupBody.clientWidth !== 0 && container.clientWidth !== popupBody.clientWidth ) { - container.style.setProperty('width', popupBody.clientWidth + 'px'); - } - popup.style.removeProperty('height'); - if ( popupBody.clientHeight !== 0 && popup.clientHeight !== popupBody.clientHeight ) { - popup.style.setProperty('height', popupBody.clientHeight + 'px'); - } - var ph = document.documentElement.clientHeight; - var crect = container.getBoundingClientRect(); - if ( crect.height > ph ) { - popup.style.setProperty('height', 'calc(' + ph + 'px - 1.8em)'); - } - // Adjust width for presence/absence of vertical scroll bar which may - // have appeared as a result of last operation. - var cw = container.clientWidth; - var dw = popup.contentWindow.document.documentElement.clientWidth; - if ( cw !== dw ) { - container.style.setProperty('width', (2 * cw - dw) + 'px'); - } - }; + var resizePopup = function () { + if (popup === null) { + return; + } + var popupBody = popup.contentWindow.document.body; + if (popupBody.clientWidth !== 0 && container.clientWidth !== popupBody.clientWidth) { + container.style.setProperty('width', popupBody.clientWidth + 'px'); + } + popup.style.removeProperty('height'); + if (popupBody.clientHeight !== 0 && popup.clientHeight !== popupBody.clientHeight) { + popup.style.setProperty('height', popupBody.clientHeight + 'px'); + } + var ph = document.documentElement.clientHeight; + var crect = container.getBoundingClientRect(); + if (crect.height > ph) { + popup.style.setProperty('height', 'calc(' + ph + 'px - 1.8em)'); + } + // Adjust width for presence/absence of vertical scroll bar which may + // have appeared as a result of last operation. + var cw = container.clientWidth; + var dw = popup.contentWindow.document.documentElement.clientWidth; + if (cw !== dw) { + container.style.setProperty('width', (2 * cw - dw) + 'px'); + } + }; - var toggleSize = function() { - container.classList.toggle('hide'); - }; + var toggleSize = function () { + container.classList.toggle('hide'); + }; - var onResizeRequested = function() { - var popupBody = popup.contentWindow.document.body; - if ( popupBody.hasAttribute('data-resize-popup') === false ) { - return; - } - popupBody.removeAttribute('data-resize-popup'); - resizePopup(); - }; + var onResizeRequested = function () { + var popupBody = popup.contentWindow.document.body; + if (popupBody.hasAttribute('data-resize-popup') === false) { + return; + } + popupBody.removeAttribute('data-resize-popup'); + resizePopup(); + }; - var onLoad = function() { - resizePopup(); - var popupBody = popup.contentDocument.body; - popupBody.removeAttribute('data-resize-popup'); - popupObserver.observe(popupBody, { - attributes: true, - attributesFilter: [ 'data-resize-popup' ] - }); - }; + var onLoad = function () { + resizePopup(); + var popupBody = popup.contentDocument.body; + popupBody.removeAttribute('data-resize-popup'); + popupObserver.observe(popupBody, { + attributes: true, + attributesFilter: ['data-resize-popup'] + }); + }; - var toggleOn = function(td) { - var tr = td.parentNode; - var matches = tr.className.match(/(?:^| )tab_([^ ]+)/); - if ( matches === null ) { - return; - } - realTabId = localTabId = matches[1]; - if ( localTabId === 'bts' ) { - realTabId = noTabId; - } + var toggleOn = function (td) { + var tr = td.parentNode; + var matches = tr.className.match(/(?:^| )tab_([^ ]+)/); + if (matches === null) { + return; + } + realTabId = localTabId = matches[1]; + if (localTabId === 'bts') { + realTabId = noTabId; + } - container = document.getElementById('popupContainer'); + container = document.getElementById('popupContainer'); - container.querySelector('div > span:nth-of-type(1)').addEventListener('click', toggleSize); - container.querySelector('div > span:nth-of-type(2)').addEventListener('click', toggleOff); + container.querySelector('div > span:nth-of-type(1)').addEventListener('click', toggleSize); + container.querySelector('div > span:nth-of-type(2)').addEventListener('click', toggleOff); - popup = document.createElement('iframe'); - popup.addEventListener('load', onLoad); - popup.setAttribute('src', 'popup.html?tabId=' + realTabId); - popupObserver = new MutationObserver(onResizeRequested); - container.appendChild(popup); + popup = document.createElement('iframe'); + popup.addEventListener('load', onLoad); + popup.setAttribute('src', 'popup.html?tabId=' + realTabId); + popupObserver = new MutationObserver(onResizeRequested); + container.appendChild(popup); - style = document.getElementById('popupFilterer'); - style.textContent = styleTemplate.replace('{{tabId}}', localTabId); + style = document.getElementById('popupFilterer'); + style.textContent = styleTemplate.replace('{{tabId}}', localTabId); - document.body.classList.add('popupOn'); - }; + document.body.classList.add('popupOn'); + }; - var toggleOff = function() { - document.body.classList.remove('popupOn'); + var toggleOff = function () { + document.body.classList.remove('popupOn'); - container.querySelector('div > span:nth-of-type(1)').removeEventListener('click', toggleSize); - container.querySelector('div > span:nth-of-type(2)').removeEventListener('click', toggleOff); - container.classList.remove('hide'); + container.querySelector('div > span:nth-of-type(1)').removeEventListener('click', toggleSize); + container.querySelector('div > span:nth-of-type(2)').removeEventListener('click', toggleOff); + container.classList.remove('hide'); - popup.removeEventListener('load', onLoad); - popupObserver.disconnect(); - popupObserver = null; - popup.setAttribute('src', ''); - container.removeChild(popup); - popup = null; + popup.removeEventListener('load', onLoad); + popupObserver.disconnect(); + popupObserver = null; + popup.setAttribute('src', ''); + container.removeChild(popup); + popup = null; - style.textContent = ''; - style = null; + style.textContent = ''; + style = null; - container = null; - realTabId = null; - }; + container = null; + realTabId = null; + }; - var exports = { - toggleOn: function(ev) { - if ( realTabId === null ) { - toggleOn(ev.target); + var exports = { + toggleOn: function (ev) { + if (realTabId === null) { + toggleOn(ev.target); + } + }, + toggleOff: function () { + if (realTabId !== null) { + toggleOff(); + } } - }, - toggleOff: function() { - if ( realTabId !== null ) { - toggleOff(); + }; + + Object.defineProperty(exports, 'tabId', { + get: function () { + return realTabId || 0; } - } - }; + }); - Object.defineProperty(exports, 'tabId', { - get: function() { return realTabId || 0; } - }); + return exports; + })(); - return exports; -})(); + /******************************************************************************/ -/******************************************************************************/ + var grabView = function () { + if (ownerId === undefined) { + ownerId = Date.now(); + } + readLogBufferAsync(); + }; -var grabView = function() { - if ( ownerId === undefined ) { - ownerId = Date.now(); - } - readLogBufferAsync(); -}; - -var releaseView = function() { - if ( ownerId === undefined ) { return; } - vAPI.messaging.send( - 'logger-ui.js', - { what: 'releaseView', ownerId: ownerId } - ); - ownerId = undefined; -}; + var releaseView = function () { + if (ownerId === undefined) { + return; + } + vAPI.messaging.send( + 'logger-ui.js', { + what: 'releaseView', + ownerId: ownerId + } + ); + ownerId = undefined; + }; -window.addEventListener('pagehide', releaseView); -window.addEventListener('pageshow', grabView); -// https://bugzilla.mozilla.org/show_bug.cgi?id=1398625 -window.addEventListener('beforeunload', releaseView); + window.addEventListener('pagehide', releaseView); + window.addEventListener('pageshow', grabView); + // https://bugzilla.mozilla.org/show_bug.cgi?id=1398625 + window.addEventListener('beforeunload', releaseView); -/******************************************************************************/ + /******************************************************************************/ -readLogBuffer(); + readLogBuffer(); -uDom('#pageSelector').on('change', pageSelectorChanged); -uDom('#refresh').on('click', refreshTab); -uDom('#compactViewToggler').on('click', toggleCompactView); -uDom('#clean').on('click', cleanBuffer); -uDom('#clear').on('click', clearBuffer); -uDom('#maxEntries').on('change', onMaxEntriesChanged); -uDom('#content table').on('click', 'tr > td:nth-of-type(1)', toggleCompactRow); -uDom('#content table').on('click', 'tr.canMtx > td:nth-of-type(2)', popupManager.toggleOn); + uDom('#pageSelector').on('change', pageSelectorChanged); + uDom('#refresh').on('click', refreshTab); + uDom('#compactViewToggler').on('click', toggleCompactView); + uDom('#clean').on('click', cleanBuffer); + uDom('#clear').on('click', clearBuffer); + uDom('#maxEntries').on('change', onMaxEntriesChanged); + uDom('#content table').on('click', 'tr > td:nth-of-type(1)', toggleCompactRow); + uDom('#content table').on('click', 'tr.canMtx > td:nth-of-type(2)', popupManager.toggleOn); -/******************************************************************************/ + /******************************************************************************/ })(); diff --git a/js/logger.js b/js/logger.js index 0a7acd2..6f742b3 100644 --- a/js/logger.js +++ b/js/logger.js @@ -53,7 +53,7 @@ var janitor = function() { if ( buffer !== null && - lastReadTime < (Date.now() - logBufferObsoleteAfter) + lastReadTime < (Date.now() - logBufferObsoleteAfter) ) { buffer = null; writePtr = 0; diff --git a/js/main-blocked.js b/js/main-blocked.js index 6e0edbc..80ac9d3 100644 --- a/js/main-blocked.js +++ b/js/main-blocked.js @@ -29,148 +29,148 @@ (function() { -/******************************************************************************/ - -var details = {}; + /******************************************************************************/ -(function() { - var matches = /details=([^&]+)/.exec(window.location.search); - if ( matches === null ) { return; } - try { - details = JSON.parse(atob(matches[1])); - } catch(ex) { - } -})(); + var details = {}; -/******************************************************************************/ + (function() { + var matches = /details=([^&]+)/.exec(window.location.search); + if ( matches === null ) { return; } + try { + details = JSON.parse(atob(matches[1])); + } catch(ex) { + } + })(); -uDom('.what').text(details.url); -// uDom('#why').text(details.why.slice(3)); + /******************************************************************************/ -/******************************************************************************/ + uDom('.what').text(details.url); + // uDom('#why').text(details.why.slice(3)); -// https://github.com/gorhill/uMatrix/issues/502 -// Code below originally imported from: -// https://github.com/gorhill/uBlock/blob/master/src/js/document-blocked.js + /******************************************************************************/ -(function() { - if ( typeof URL !== 'function' ) { return; } + // https://github.com/gorhill/uMatrix/issues/502 + // Code below originally imported from: + // https://github.com/gorhill/uBlock/blob/master/src/js/document-blocked.js - var reURL = /^https?:\/\//; + (function() { + if ( typeof URL !== 'function' ) { return; } - var liFromParam = function(name, value) { - if ( value === '' ) { - value = name; - name = ''; - } - var li = document.createElement('li'); - var span = document.createElement('span'); - span.textContent = name; - li.appendChild(span); - if ( name !== '' && value !== '' ) { - li.appendChild(document.createTextNode(' = ')); - } - span = document.createElement('span'); - if ( reURL.test(value) ) { - var a = document.createElement('a'); - a.href = a.textContent = value; - span.appendChild(a); - } else { - span.textContent = value; - } - li.appendChild(span); - return li; - }; + var reURL = /^https?:\/\//; - var safeDecodeURIComponent = function(s) { - try { - s = decodeURIComponent(s); - } catch (ex) { - } - return s; - }; - - var renderParams = function(parentNode, rawURL) { - var a = document.createElement('a'); - a.href = rawURL; - if ( a.search.length === 0 ) { return false; } - - var pos = rawURL.indexOf('?'); - var li = liFromParam( - vAPI.i18n('docblockedNoParamsPrompt'), - rawURL.slice(0, pos) - ); - parentNode.appendChild(li); - - var params = a.search.slice(1).split('&'); - var param, name, value, ul; - for ( var i = 0; i < params.length; i++ ) { - param = params[i]; - pos = param.indexOf('='); - if ( pos === -1 ) { - pos = param.length; + var liFromParam = function(name, value) { + if ( value === '' ) { + value = name; + name = ''; + } + var li = document.createElement('li'); + var span = document.createElement('span'); + span.textContent = name; + li.appendChild(span); + if ( name !== '' && value !== '' ) { + li.appendChild(document.createTextNode(' = ')); } - name = safeDecodeURIComponent(param.slice(0, pos)); - value = safeDecodeURIComponent(param.slice(pos + 1)); - li = liFromParam(name, value); + span = document.createElement('span'); if ( reURL.test(value) ) { - ul = document.createElement('ul'); - renderParams(ul, value); - li.appendChild(ul); + var a = document.createElement('a'); + a.href = a.textContent = value; + span.appendChild(a); + } else { + span.textContent = value; } + li.appendChild(span); + return li; + }; + + var safeDecodeURIComponent = function(s) { + try { + s = decodeURIComponent(s); + } catch (ex) { + } + return s; + }; + + var renderParams = function(parentNode, rawURL) { + var a = document.createElement('a'); + a.href = rawURL; + if ( a.search.length === 0 ) { return false; } + + var pos = rawURL.indexOf('?'); + var li = liFromParam( + vAPI.i18n('docblockedNoParamsPrompt'), + rawURL.slice(0, pos) + ); parentNode.appendChild(li); - } - return true; - }; - if ( renderParams(uDom.nodeFromId('parsed'), details.url) === false ) { - return; - } + var params = a.search.slice(1).split('&'); + var param, name, value, ul; + for ( var i = 0; i < params.length; i++ ) { + param = params[i]; + pos = param.indexOf('='); + if ( pos === -1 ) { + pos = param.length; + } + name = safeDecodeURIComponent(param.slice(0, pos)); + value = safeDecodeURIComponent(param.slice(pos + 1)); + li = liFromParam(name, value); + if ( reURL.test(value) ) { + ul = document.createElement('ul'); + renderParams(ul, value); + li.appendChild(ul); + } + parentNode.appendChild(li); + } + return true; + }; - var toggler = document.createElement('span'); - toggler.className = 'fa'; - uDom('#theURL > p').append(toggler); + if ( renderParams(uDom.nodeFromId('parsed'), details.url) === false ) { + return; + } - uDom(toggler).on('click', function() { - var collapsed = uDom.nodeFromId('theURL').classList.toggle('collapsed'); - vAPI.localStorage.setItem( - 'document-blocked-collapse-url', - collapsed.toString() + var toggler = document.createElement('span'); + toggler.className = 'fa'; + uDom('#theURL > p').append(toggler); + + uDom(toggler).on('click', function() { + var collapsed = uDom.nodeFromId('theURL').classList.toggle('collapsed'); + vAPI.localStorage.setItem( + 'document-blocked-collapse-url', + collapsed.toString() + ); + }); + + uDom.nodeFromId('theURL').classList.toggle( + 'collapsed', + vAPI.localStorage.getItem('document-blocked-collapse-url') === 'true' ); - }); + })(); - uDom.nodeFromId('theURL').classList.toggle( - 'collapsed', - vAPI.localStorage.getItem('document-blocked-collapse-url') === 'true' - ); -})(); + /******************************************************************************/ -/******************************************************************************/ - -if ( window.history.length > 1 ) { - uDom('#back').on('click', function() { window.history.back(); }); - uDom('#bye').css('display', 'none'); -} else { - uDom('#bye').on('click', function() { window.close(); }); - uDom('#back').css('display', 'none'); -} + if ( window.history.length > 1 ) { + uDom('#back').on('click', function() { window.history.back(); }); + uDom('#bye').css('display', 'none'); + } else { + uDom('#bye').on('click', function() { window.close(); }); + uDom('#back').css('display', 'none'); + } -/******************************************************************************/ + /******************************************************************************/ -// See if the target hostname is still blacklisted, and if not, navigate to it. + // See if the target hostname is still blacklisted, and if not, navigate to it. -vAPI.messaging.send('main-blocked.js', { - what: 'mustBlock', - scope: details.hn, - hostname: details.hn, - type: 'doc' -}, function(response) { - if ( response === false ) { - window.location.replace(details.url); - } -}); + vAPI.messaging.send('main-blocked.js', { + what: 'mustBlock', + scope: details.hn, + hostname: details.hn, + type: 'doc' + }, function(response) { + if ( response === false ) { + window.location.replace(details.url); + } + }); -/******************************************************************************/ + /******************************************************************************/ })(); diff --git a/js/matrix.js b/js/matrix.js index 9f2de51..6af5ab1 100644 --- a/js/matrix.js +++ b/js/matrix.js @@ -30,844 +30,844 @@ ηMatrix.Matrix = (function() { -/******************************************************************************/ - -var ηm = ηMatrix; -var magicId = 'axyorpwxtmnf'; -var uniqueIdGenerator = 1; + /******************************************************************************/ -/******************************************************************************/ + var ηm = ηMatrix; + var magicId = 'axyorpwxtmnf'; + var uniqueIdGenerator = 1; -var Matrix = function() { - this.id = uniqueIdGenerator++; - this.reset(); - this.sourceRegister = ''; - this.decomposedSourceRegister = ['']; - this.specificityRegister = 0; -}; + /******************************************************************************/ -/******************************************************************************/ - -Matrix.Transparent = 0; -Matrix.Red = 1; -Matrix.Green = 2; -Matrix.Gray = 3; + var Matrix = function() { + this.id = uniqueIdGenerator++; + this.reset(); + this.sourceRegister = ''; + this.decomposedSourceRegister = ['']; + this.specificityRegister = 0; + }; -Matrix.Indirect = 0x00; -Matrix.Direct = 0x80; + /******************************************************************************/ + + Matrix.Transparent = 0; + Matrix.Red = 1; + Matrix.Green = 2; + Matrix.Gray = 3; + + Matrix.Indirect = 0x00; + Matrix.Direct = 0x80; + + Matrix.RedDirect = Matrix.Red | Matrix.Direct; + Matrix.RedIndirect = Matrix.Red | Matrix.Indirect; + Matrix.GreenDirect = Matrix.Green | Matrix.Direct; + Matrix.GreenIndirect = Matrix.Green | Matrix.Indirect; + Matrix.GrayDirect = Matrix.Gray | Matrix.Direct; + Matrix.GrayIndirect = Matrix.Gray | Matrix.Indirect; + + /******************************************************************************/ + + var typeBitOffsets = new Map([ + [ '*', 0 ], + [ 'doc', 2 ], + [ 'cookie', 4 ], + [ 'css', 6 ], + [ 'image', 8 ], + [ 'media', 10 ], + [ 'script', 12 ], + [ 'xhr', 14 ], + [ 'frame', 16 ], + [ 'other', 18 ] + ]); + + var stateToNameMap = new Map([ + [ 1, 'block' ], + [ 2, 'allow' ], + [ 3, 'inherit' ] + ]); + + var nameToStateMap = { + 'block': 1, + 'allow': 2, + 'noop': 2, + 'inherit': 3 + }; -Matrix.RedDirect = Matrix.Red | Matrix.Direct; -Matrix.RedIndirect = Matrix.Red | Matrix.Indirect; -Matrix.GreenDirect = Matrix.Green | Matrix.Direct; -Matrix.GreenIndirect = Matrix.Green | Matrix.Indirect; -Matrix.GrayDirect = Matrix.Gray | Matrix.Direct; -Matrix.GrayIndirect = Matrix.Gray | Matrix.Indirect; + var switchBitOffsets = new Map([ + [ 'matrix-off', 0 ], + [ 'https-strict', 2 ], + /* 4 is now unused, formerly assigned to UA spoofing */ + [ 'referrer-spoof', 6 ], + [ 'noscript-spoof', 8 ], + [ 'no-workers', 10 ] + ]); + + var switchStateToNameMap = new Map([ + [ 1, 'true' ], + [ 2, 'false' ] + ]); + + var nameToSwitchStateMap = { + 'true': 1, + 'false': 2 + }; -/******************************************************************************/ + /******************************************************************************/ -var typeBitOffsets = new Map([ - [ '*', 0 ], - [ 'doc', 2 ], - [ 'cookie', 4 ], - [ 'css', 6 ], - [ 'image', 8 ], - [ 'media', 10 ], - [ 'script', 12 ], - [ 'xhr', 14 ], - [ 'frame', 16 ], - [ 'other', 18 ] -]); - -var stateToNameMap = new Map([ - [ 1, 'block' ], - [ 2, 'allow' ], - [ 3, 'inherit' ] -]); - -var nameToStateMap = { - 'block': 1, - 'allow': 2, - 'noop': 2, - 'inherit': 3 -}; - -var switchBitOffsets = new Map([ - [ 'matrix-off', 0 ], - [ 'https-strict', 2 ], - /* 4 is now unused, formerly assigned to UA spoofing */ - [ 'referrer-spoof', 6 ], - [ 'noscript-spoof', 8 ], - [ 'no-workers', 10 ] -]); - -var switchStateToNameMap = new Map([ - [ 1, 'true' ], - [ 2, 'false' ] -]); - -var nameToSwitchStateMap = { - 'true': 1, - 'false': 2 -}; + Matrix.columnHeaderIndices = (function() { + var out = new Map(), + i = 0; + for ( var type of typeBitOffsets.keys() ) { + out.set(type, i++); + } + return out; + })(); -/******************************************************************************/ -Matrix.columnHeaderIndices = (function() { - var out = new Map(), - i = 0; - for ( var type of typeBitOffsets.keys() ) { - out.set(type, i++); - } - return out; -})(); + Matrix.switchNames = new Set(switchBitOffsets.keys()); + /******************************************************************************/ -Matrix.switchNames = new Set(switchBitOffsets.keys()); + // For performance purpose, as simple tests as possible + var reHostnameVeryCoarse = /[g-z_-]/; + var reIPv4VeryCoarse = /\.\d+$/; -/******************************************************************************/ + // http://tools.ietf.org/html/rfc5952 + // 4.3: "MUST be represented in lowercase" + // Also: http://en.wikipedia.org/wiki/IPv6_address#Literal_IPv6_addresses_in_network_resource_identifiers -// For performance purpose, as simple tests as possible -var reHostnameVeryCoarse = /[g-z_-]/; -var reIPv4VeryCoarse = /\.\d+$/; + var isIPAddress = function(hostname) { + if ( reHostnameVeryCoarse.test(hostname) ) { + return false; + } + if ( reIPv4VeryCoarse.test(hostname) ) { + return true; + } + return hostname.charAt(0) === '['; + }; -// http://tools.ietf.org/html/rfc5952 -// 4.3: "MUST be represented in lowercase" -// Also: http://en.wikipedia.org/wiki/IPv6_address#Literal_IPv6_addresses_in_network_resource_identifiers + /******************************************************************************/ -var isIPAddress = function(hostname) { - if ( reHostnameVeryCoarse.test(hostname) ) { - return false; - } - if ( reIPv4VeryCoarse.test(hostname) ) { - return true; - } - return hostname.charAt(0) === '['; -}; + var toBroaderHostname = function(hostname) { + if ( hostname === '*' ) { return ''; } + if ( isIPAddress(hostname) ) { + return toBroaderIPAddress(hostname); + } + var pos = hostname.indexOf('.'); + if ( pos === -1 ) { + return '*'; + } + return hostname.slice(pos + 1); + }; -/******************************************************************************/ + var toBroaderIPAddress = function(ipaddress) { + // Can't broaden IPv6 (for now) + if ( ipaddress.charAt(0) === '[' ) { + return '*'; + } + var pos = ipaddress.lastIndexOf('.'); + return pos !== -1 ? ipaddress.slice(0, pos) : '*'; + }; -var toBroaderHostname = function(hostname) { - if ( hostname === '*' ) { return ''; } - if ( isIPAddress(hostname) ) { - return toBroaderIPAddress(hostname); - } - var pos = hostname.indexOf('.'); - if ( pos === -1 ) { - return '*'; - } - return hostname.slice(pos + 1); -}; - -var toBroaderIPAddress = function(ipaddress) { - // Can't broaden IPv6 (for now) - if ( ipaddress.charAt(0) === '[' ) { - return '*'; - } - var pos = ipaddress.lastIndexOf('.'); - return pos !== -1 ? ipaddress.slice(0, pos) : '*'; -}; - -Matrix.toBroaderHostname = toBroaderHostname; + Matrix.toBroaderHostname = toBroaderHostname; -/******************************************************************************/ + /******************************************************************************/ -// Find out src-des relationship, using coarse-to-fine grained tests for -// speed. If desHostname is 1st-party to srcHostname, the domain is returned, -// otherwise the empty string. + // Find out src-des relationship, using coarse-to-fine grained tests for + // speed. If desHostname is 1st-party to srcHostname, the domain is returned, + // otherwise the empty string. -var extractFirstPartyDesDomain = function(srcHostname, desHostname) { - if ( srcHostname === '*' || desHostname === '*' || desHostname === '1st-party' ) { - return ''; - } - var ηmuri = ηm.URI; - var srcDomain = ηmuri.domainFromHostname(srcHostname) || srcHostname; - var desDomain = ηmuri.domainFromHostname(desHostname) || desHostname; - return desDomain === srcDomain ? desDomain : ''; -}; + var extractFirstPartyDesDomain = function(srcHostname, desHostname) { + if ( srcHostname === '*' || desHostname === '*' || desHostname === '1st-party' ) { + return ''; + } + var ηmuri = ηm.URI; + var srcDomain = ηmuri.domainFromHostname(srcHostname) || srcHostname; + var desDomain = ηmuri.domainFromHostname(desHostname) || desHostname; + return desDomain === srcDomain ? desDomain : ''; + }; -/******************************************************************************/ + /******************************************************************************/ -Matrix.prototype.reset = function() { - this.switches = new Map(); - this.rules = new Map(); - this.rootValue = Matrix.RedIndirect; - this.modifiedTime = 0; -}; + Matrix.prototype.reset = function() { + this.switches = new Map(); + this.rules = new Map(); + this.rootValue = Matrix.RedIndirect; + this.modifiedTime = 0; + }; -/******************************************************************************/ + /******************************************************************************/ -Matrix.prototype.decomposeSource = function(srcHostname) { - if ( srcHostname === this.sourceRegister ) { return; } - var hn = srcHostname; - this.decomposedSourceRegister[0] = this.sourceRegister = hn; - var i = 1; - for (;;) { - hn = toBroaderHostname(hn); - this.decomposedSourceRegister[i++] = hn; - if ( hn === '' ) { break; } - } -}; + Matrix.prototype.decomposeSource = function(srcHostname) { + if ( srcHostname === this.sourceRegister ) { return; } + var hn = srcHostname; + this.decomposedSourceRegister[0] = this.sourceRegister = hn; + var i = 1; + for (;;) { + hn = toBroaderHostname(hn); + this.decomposedSourceRegister[i++] = hn; + if ( hn === '' ) { break; } + } + }; -/******************************************************************************/ + /******************************************************************************/ -// Copy another matrix to self. Do this incrementally to minimize impact on -// a live matrix. + // Copy another matrix to self. Do this incrementally to minimize impact on + // a live matrix. -Matrix.prototype.assign = function(other) { - var k, entry; - // Remove rules not in other - for ( k of this.rules.keys() ) { - if ( other.rules.has(k) === false ) { - this.rules.delete(k); + Matrix.prototype.assign = function(other) { + var k, entry; + // Remove rules not in other + for ( k of this.rules.keys() ) { + if ( other.rules.has(k) === false ) { + this.rules.delete(k); + } + } + // Remove switches not in other + for ( k of this.switches.keys() ) { + if ( other.switches.has(k) === false ) { + this.switches.delete(k); + } + } + // Add/change rules in other + for ( entry of other.rules ) { + this.rules.set(entry[0], entry[1]); } - } - // Remove switches not in other - for ( k of this.switches.keys() ) { - if ( other.switches.has(k) === false ) { - this.switches.delete(k); - } - } - // Add/change rules in other - for ( entry of other.rules ) { - this.rules.set(entry[0], entry[1]); - } - // Add/change switches in other - for ( entry of other.switches ) { - this.switches.set(entry[0], entry[1]); - } - this.modifiedTime = other.modifiedTime; - return this; -}; - -// https://www.youtube.com/watch?v=e9RS4biqyAc + // Add/change switches in other + for ( entry of other.switches ) { + this.switches.set(entry[0], entry[1]); + } + this.modifiedTime = other.modifiedTime; + return this; + }; -/******************************************************************************/ + // https://www.youtube.com/watch?v=e9RS4biqyAc -// If value is undefined, the switch is removed + /******************************************************************************/ -Matrix.prototype.setSwitch = function(switchName, srcHostname, newVal) { - var bitOffset = switchBitOffsets.get(switchName); - if ( bitOffset === undefined ) { - return false; - } - if ( newVal === this.evaluateSwitch(switchName, srcHostname) ) { - return false; - } - var bits = this.switches.get(srcHostname) || 0; - bits &= ~(3 << bitOffset); - bits |= newVal << bitOffset; - if ( bits === 0 ) { - this.switches.delete(srcHostname); - } else { - this.switches.set(srcHostname, bits); - } - this.modifiedTime = Date.now(); - return true; -}; + // If value is undefined, the switch is removed -/******************************************************************************/ + Matrix.prototype.setSwitch = function(switchName, srcHostname, newVal) { + var bitOffset = switchBitOffsets.get(switchName); + if ( bitOffset === undefined ) { + return false; + } + if ( newVal === this.evaluateSwitch(switchName, srcHostname) ) { + return false; + } + var bits = this.switches.get(srcHostname) || 0; + bits &= ~(3 << bitOffset); + bits |= newVal << bitOffset; + if ( bits === 0 ) { + this.switches.delete(srcHostname); + } else { + this.switches.set(srcHostname, bits); + } + this.modifiedTime = Date.now(); + return true; + }; -Matrix.prototype.setCell = function(srcHostname, desHostname, type, state) { - var bitOffset = typeBitOffsets.get(type), - k = srcHostname + ' ' + desHostname, - oldBitmap = this.rules.get(k); - if ( oldBitmap === undefined ) { - oldBitmap = 0; - } - var newBitmap = oldBitmap & ~(3 << bitOffset) | (state << bitOffset); - if ( newBitmap === oldBitmap ) { - return false; - } - if ( newBitmap === 0 ) { - this.rules.delete(k); - } else { - this.rules.set(k, newBitmap); - } - this.modifiedTime = Date.now(); - return true; -}; + /******************************************************************************/ -/******************************************************************************/ + Matrix.prototype.setCell = function(srcHostname, desHostname, type, state) { + var bitOffset = typeBitOffsets.get(type), + k = srcHostname + ' ' + desHostname, + oldBitmap = this.rules.get(k); + if ( oldBitmap === undefined ) { + oldBitmap = 0; + } + var newBitmap = oldBitmap & ~(3 << bitOffset) | (state << bitOffset); + if ( newBitmap === oldBitmap ) { + return false; + } + if ( newBitmap === 0 ) { + this.rules.delete(k); + } else { + this.rules.set(k, newBitmap); + } + this.modifiedTime = Date.now(); + return true; + }; -Matrix.prototype.blacklistCell = function(srcHostname, desHostname, type) { - var r = this.evaluateCellZ(srcHostname, desHostname, type); - if ( r === 1 ) { - return false; - } - this.setCell(srcHostname, desHostname, type, 0); - r = this.evaluateCellZ(srcHostname, desHostname, type); - if ( r === 1 ) { + /******************************************************************************/ + + Matrix.prototype.blacklistCell = function(srcHostname, desHostname, type) { + var r = this.evaluateCellZ(srcHostname, desHostname, type); + if ( r === 1 ) { + return false; + } + this.setCell(srcHostname, desHostname, type, 0); + r = this.evaluateCellZ(srcHostname, desHostname, type); + if ( r === 1 ) { + return true; + } + this.setCell(srcHostname, desHostname, type, 1); return true; - } - this.setCell(srcHostname, desHostname, type, 1); - return true; -}; + }; -/******************************************************************************/ + /******************************************************************************/ -Matrix.prototype.whitelistCell = function(srcHostname, desHostname, type) { - var r = this.evaluateCellZ(srcHostname, desHostname, type); - if ( r === 2 ) { - return false; - } - this.setCell(srcHostname, desHostname, type, 0); - r = this.evaluateCellZ(srcHostname, desHostname, type); - if ( r === 2 ) { + Matrix.prototype.whitelistCell = function(srcHostname, desHostname, type) { + var r = this.evaluateCellZ(srcHostname, desHostname, type); + if ( r === 2 ) { + return false; + } + this.setCell(srcHostname, desHostname, type, 0); + r = this.evaluateCellZ(srcHostname, desHostname, type); + if ( r === 2 ) { + return true; + } + this.setCell(srcHostname, desHostname, type, 2); return true; - } - this.setCell(srcHostname, desHostname, type, 2); - return true; -}; + }; -/******************************************************************************/ + /******************************************************************************/ -Matrix.prototype.graylistCell = function(srcHostname, desHostname, type) { - var r = this.evaluateCellZ(srcHostname, desHostname, type); - if ( r === 0 || r === 3 ) { - return false; - } - this.setCell(srcHostname, desHostname, type, 0); - r = this.evaluateCellZ(srcHostname, desHostname, type); - if ( r === 0 || r === 3 ) { + Matrix.prototype.graylistCell = function(srcHostname, desHostname, type) { + var r = this.evaluateCellZ(srcHostname, desHostname, type); + if ( r === 0 || r === 3 ) { + return false; + } + this.setCell(srcHostname, desHostname, type, 0); + r = this.evaluateCellZ(srcHostname, desHostname, type); + if ( r === 0 || r === 3 ) { + return true; + } + this.setCell(srcHostname, desHostname, type, 3); return true; - } - this.setCell(srcHostname, desHostname, type, 3); - return true; -}; + }; -/******************************************************************************/ + /******************************************************************************/ -Matrix.prototype.evaluateCell = function(srcHostname, desHostname, type) { - var key = srcHostname + ' ' + desHostname; - var bitmap = this.rules.get(key); - if ( bitmap === undefined ) { - return 0; - } - return bitmap >> typeBitOffsets.get(type) & 3; -}; + Matrix.prototype.evaluateCell = function(srcHostname, desHostname, type) { + var key = srcHostname + ' ' + desHostname; + var bitmap = this.rules.get(key); + if ( bitmap === undefined ) { + return 0; + } + return bitmap >> typeBitOffsets.get(type) & 3; + }; -/******************************************************************************/ + /******************************************************************************/ + + Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) { + this.decomposeSource(srcHostname); -Matrix.prototype.evaluateCellZ = function(srcHostname, desHostname, type) { - this.decomposeSource(srcHostname); - - var bitOffset = typeBitOffsets.get(type), - s, v, i = 0; - for (;;) { - s = this.decomposedSourceRegister[i++]; - if ( s === '' ) { break; } - v = this.rules.get(s + ' ' + desHostname); - if ( v !== undefined ) { - v = v >> bitOffset & 3; - if ( v !== 0 ) { - return v; + var bitOffset = typeBitOffsets.get(type), + s, v, i = 0; + for (;;) { + s = this.decomposedSourceRegister[i++]; + if ( s === '' ) { break; } + v = this.rules.get(s + ' ' + desHostname); + if ( v !== undefined ) { + v = v >> bitOffset & 3; + if ( v !== 0 ) { + return v; + } } } - } - // srcHostname is '*' at this point + // srcHostname is '*' at this point - // Preset blacklisted hostnames are blacklisted in global scope - if ( type === '*' && ηm.ubiquitousBlacklist.test(desHostname) ) { - return 1; - } + // Preset blacklisted hostnames are blacklisted in global scope + if ( type === '*' && ηm.ubiquitousBlacklist.test(desHostname) ) { + return 1; + } - // https://github.com/gorhill/uMatrix/issues/65 - // Hardcoded global `doc` rule - if ( type === 'doc' && desHostname === '*' ) { - return 2; - } + // https://github.com/gorhill/uMatrix/issues/65 + // Hardcoded global `doc` rule + if ( type === 'doc' && desHostname === '*' ) { + return 2; + } - return 0; -}; + return 0; + }; -/******************************************************************************/ + /******************************************************************************/ -Matrix.prototype.evaluateCellZXY = function(srcHostname, desHostname, type) { - // Matrix filtering switch - this.specificityRegister = 0; - if ( this.evaluateSwitchZ('matrix-off', srcHostname) ) { - return Matrix.GreenIndirect; - } - - // TODO: There are cells evaluated twice when the type is '*'. Unsure - // whether it's worth trying to avoid that, as this could introduce - // overhead which may not be gained back by skipping the redundant tests. - // And this happens *only* when building the matrix UI, not when - // evaluating net requests. - - // Specific-hostname specific-type cell - this.specificityRegister = 1; - var r = this.evaluateCellZ(srcHostname, desHostname, type); - if ( r === 1 ) { return Matrix.RedDirect; } - if ( r === 2 ) { return Matrix.GreenDirect; } - - // Specific-hostname any-type cell - this.specificityRegister = 2; - var rl = this.evaluateCellZ(srcHostname, desHostname, '*'); - if ( rl === 1 ) { return Matrix.RedIndirect; } - - var d = desHostname; - var firstPartyDesDomain = extractFirstPartyDesDomain(srcHostname, desHostname); - - // Ancestor cells, up to 1st-party destination domain - if ( firstPartyDesDomain !== '' ) { - this.specificityRegister = 3; + Matrix.prototype.evaluateCellZXY = function(srcHostname, desHostname, type) { + // Matrix filtering switch + this.specificityRegister = 0; + if ( this.evaluateSwitchZ('matrix-off', srcHostname) ) { + return Matrix.GreenIndirect; + } + + // TODO: There are cells evaluated twice when the type is '*'. Unsure + // whether it's worth trying to avoid that, as this could introduce + // overhead which may not be gained back by skipping the redundant tests. + // And this happens *only* when building the matrix UI, not when + // evaluating net requests. + + // Specific-hostname specific-type cell + this.specificityRegister = 1; + var r = this.evaluateCellZ(srcHostname, desHostname, type); + if ( r === 1 ) { return Matrix.RedDirect; } + if ( r === 2 ) { return Matrix.GreenDirect; } + + // Specific-hostname any-type cell + this.specificityRegister = 2; + var rl = this.evaluateCellZ(srcHostname, desHostname, '*'); + if ( rl === 1 ) { return Matrix.RedIndirect; } + + var d = desHostname; + var firstPartyDesDomain = extractFirstPartyDesDomain(srcHostname, desHostname); + + // Ancestor cells, up to 1st-party destination domain + if ( firstPartyDesDomain !== '' ) { + this.specificityRegister = 3; + for (;;) { + if ( d === firstPartyDesDomain ) { break; } + d = d.slice(d.indexOf('.') + 1); + + // specific-hostname specific-type cell + r = this.evaluateCellZ(srcHostname, d, type); + if ( r === 1 ) { return Matrix.RedIndirect; } + if ( r === 2 ) { return Matrix.GreenIndirect; } + // Do not override a narrower rule + if ( rl !== 2 ) { + rl = this.evaluateCellZ(srcHostname, d, '*'); + if ( rl === 1 ) { return Matrix.RedIndirect; } + } + } + + // 1st-party specific-type cell: it's a special row, looked up only + // when destination is 1st-party to source. + r = this.evaluateCellZ(srcHostname, '1st-party', type); + if ( r === 1 ) { return Matrix.RedIndirect; } + if ( r === 2 ) { return Matrix.GreenIndirect; } + // Do not override narrower rule + if ( rl !== 2 ) { + rl = this.evaluateCellZ(srcHostname, '1st-party', '*'); + if ( rl === 1 ) { return Matrix.RedIndirect; } + } + } + + // Keep going, up to root + this.specificityRegister = 4; for (;;) { - if ( d === firstPartyDesDomain ) { break; } - d = d.slice(d.indexOf('.') + 1); + d = toBroaderHostname(d); + if ( d === '*' ) { break; } // specific-hostname specific-type cell r = this.evaluateCellZ(srcHostname, d, type); if ( r === 1 ) { return Matrix.RedIndirect; } if ( r === 2 ) { return Matrix.GreenIndirect; } - // Do not override a narrower rule + // Do not override narrower rule if ( rl !== 2 ) { rl = this.evaluateCellZ(srcHostname, d, '*'); if ( rl === 1 ) { return Matrix.RedIndirect; } } } - // 1st-party specific-type cell: it's a special row, looked up only - // when destination is 1st-party to source. - r = this.evaluateCellZ(srcHostname, '1st-party', type); + // Any-hostname specific-type cells + this.specificityRegister = 5; + r = this.evaluateCellZ(srcHostname, '*', type); + // Line below is strict-blocking if ( r === 1 ) { return Matrix.RedIndirect; } + // Narrower rule wins + if ( rl === 2 ) { return Matrix.GreenIndirect; } if ( r === 2 ) { return Matrix.GreenIndirect; } - // Do not override narrower rule - if ( rl !== 2 ) { - rl = this.evaluateCellZ(srcHostname, '1st-party', '*'); - if ( rl === 1 ) { return Matrix.RedIndirect; } - } - } - // Keep going, up to root - this.specificityRegister = 4; - for (;;) { - d = toBroaderHostname(d); - if ( d === '*' ) { break; } - - // specific-hostname specific-type cell - r = this.evaluateCellZ(srcHostname, d, type); + // Any-hostname any-type cell + this.specificityRegister = 6; + r = this.evaluateCellZ(srcHostname, '*', '*'); if ( r === 1 ) { return Matrix.RedIndirect; } if ( r === 2 ) { return Matrix.GreenIndirect; } - // Do not override narrower rule - if ( rl !== 2 ) { - rl = this.evaluateCellZ(srcHostname, d, '*'); - if ( rl === 1 ) { return Matrix.RedIndirect; } - } - } - - // Any-hostname specific-type cells - this.specificityRegister = 5; - r = this.evaluateCellZ(srcHostname, '*', type); - // Line below is strict-blocking - if ( r === 1 ) { return Matrix.RedIndirect; } - // Narrower rule wins - if ( rl === 2 ) { return Matrix.GreenIndirect; } - if ( r === 2 ) { return Matrix.GreenIndirect; } - - // Any-hostname any-type cell - this.specificityRegister = 6; - r = this.evaluateCellZ(srcHostname, '*', '*'); - if ( r === 1 ) { return Matrix.RedIndirect; } - if ( r === 2 ) { return Matrix.GreenIndirect; } - return this.rootValue; -}; - -// https://www.youtube.com/watch?v=4C5ZkwrnVfM + return this.rootValue; + }; -/******************************************************************************/ + // https://www.youtube.com/watch?v=4C5ZkwrnVfM -Matrix.prototype.evaluateRowZXY = function(srcHostname, desHostname) { - var out = []; - for ( var type of typeBitOffsets.keys() ) { - out.push(this.evaluateCellZXY(srcHostname, desHostname, type)); - } - return out; -}; + /******************************************************************************/ -/******************************************************************************/ + Matrix.prototype.evaluateRowZXY = function(srcHostname, desHostname) { + var out = []; + for ( var type of typeBitOffsets.keys() ) { + out.push(this.evaluateCellZXY(srcHostname, desHostname, type)); + } + return out; + }; -Matrix.prototype.mustBlock = function(srcHostname, desHostname, type) { - return (this.evaluateCellZXY(srcHostname, desHostname, type) & 3) === Matrix.Red; -}; + /******************************************************************************/ -/******************************************************************************/ + Matrix.prototype.mustBlock = function(srcHostname, desHostname, type) { + return (this.evaluateCellZXY(srcHostname, desHostname, type) & 3) === Matrix.Red; + }; -Matrix.prototype.srcHostnameFromRule = function(rule) { - return rule.slice(0, rule.indexOf(' ')); -}; + /******************************************************************************/ -/******************************************************************************/ + Matrix.prototype.srcHostnameFromRule = function(rule) { + return rule.slice(0, rule.indexOf(' ')); + }; -Matrix.prototype.desHostnameFromRule = function(rule) { - return rule.slice(rule.indexOf(' ') + 1); -}; + /******************************************************************************/ -/******************************************************************************/ + Matrix.prototype.desHostnameFromRule = function(rule) { + return rule.slice(rule.indexOf(' ') + 1); + }; -Matrix.prototype.setSwitchZ = function(switchName, srcHostname, newState) { - var bitOffset = switchBitOffsets.get(switchName); - if ( bitOffset === undefined ) { - return false; - } - var state = this.evaluateSwitchZ(switchName, srcHostname); - if ( newState === state ) { - return false; - } - if ( newState === undefined ) { - newState = !state; - } - var bits = this.switches.get(srcHostname) || 0; - bits &= ~(3 << bitOffset); - if ( bits === 0 ) { - this.switches.delete(srcHostname); - } else { - this.switches.set(srcHostname, bits); - } - this.modifiedTime = Date.now(); - state = this.evaluateSwitchZ(switchName, srcHostname); - if ( state === newState ) { + /******************************************************************************/ + + Matrix.prototype.setSwitchZ = function(switchName, srcHostname, newState) { + var bitOffset = switchBitOffsets.get(switchName); + if ( bitOffset === undefined ) { + return false; + } + var state = this.evaluateSwitchZ(switchName, srcHostname); + if ( newState === state ) { + return false; + } + if ( newState === undefined ) { + newState = !state; + } + var bits = this.switches.get(srcHostname) || 0; + bits &= ~(3 << bitOffset); + if ( bits === 0 ) { + this.switches.delete(srcHostname); + } else { + this.switches.set(srcHostname, bits); + } + this.modifiedTime = Date.now(); + state = this.evaluateSwitchZ(switchName, srcHostname); + if ( state === newState ) { + return true; + } + this.switches.set(srcHostname, bits | ((newState ? 1 : 2) << bitOffset)); return true; - } - this.switches.set(srcHostname, bits | ((newState ? 1 : 2) << bitOffset)); - return true; -}; + }; -/******************************************************************************/ + /******************************************************************************/ -// 0 = inherit from broader scope, up to default state -// 1 = non-default state -// 2 = forced default state (to override a broader non-default state) + // 0 = inherit from broader scope, up to default state + // 1 = non-default state + // 2 = forced default state (to override a broader non-default state) -Matrix.prototype.evaluateSwitch = function(switchName, srcHostname) { - var bits = this.switches.get(srcHostname) || 0; - if ( bits === 0 ) { - return 0; - } - var bitOffset = switchBitOffsets.get(switchName); - if ( bitOffset === undefined ) { - return 0; - } - return (bits >> bitOffset) & 3; -}; + Matrix.prototype.evaluateSwitch = function(switchName, srcHostname) { + var bits = this.switches.get(srcHostname) || 0; + if ( bits === 0 ) { + return 0; + } + var bitOffset = switchBitOffsets.get(switchName); + if ( bitOffset === undefined ) { + return 0; + } + return (bits >> bitOffset) & 3; + }; -/******************************************************************************/ + /******************************************************************************/ -Matrix.prototype.evaluateSwitchZ = function(switchName, srcHostname) { - var bitOffset = switchBitOffsets.get(switchName); - if ( bitOffset === undefined ) { return false; } + Matrix.prototype.evaluateSwitchZ = function(switchName, srcHostname) { + var bitOffset = switchBitOffsets.get(switchName); + if ( bitOffset === undefined ) { return false; } - this.decomposeSource(srcHostname); + this.decomposeSource(srcHostname); - var s, bits, i = 0; - for (;;) { - s = this.decomposedSourceRegister[i++]; - if ( s === '' ) { break; } - bits = this.switches.get(s) || 0; - if ( bits !== 0 ) { - bits = bits >> bitOffset & 3; + var s, bits, i = 0; + for (;;) { + s = this.decomposedSourceRegister[i++]; + if ( s === '' ) { break; } + bits = this.switches.get(s) || 0; if ( bits !== 0 ) { - return bits === 1; + bits = bits >> bitOffset & 3; + if ( bits !== 0 ) { + return bits === 1; + } } } - } - return false; -}; - -/******************************************************************************/ - -Matrix.prototype.extractAllSourceHostnames = (function() { - var cachedResult = new Set(); - var matrixId = 0; - var readTime = 0; + return false; + }; - return function() { - if ( matrixId !== this.id || readTime !== this.modifiedTime ) { - cachedResult.clear(); - for ( var rule of this.rules.keys() ) { - cachedResult.add(rule.slice(0, rule.indexOf(' '))); + /******************************************************************************/ + + Matrix.prototype.extractAllSourceHostnames = (function() { + var cachedResult = new Set(); + var matrixId = 0; + var readTime = 0; + + return function() { + if ( matrixId !== this.id || readTime !== this.modifiedTime ) { + cachedResult.clear(); + for ( var rule of this.rules.keys() ) { + cachedResult.add(rule.slice(0, rule.indexOf(' '))); + } + matrixId = this.id; + readTime = this.modifiedTime; + } + return cachedResult; + }; + })(); + + /******************************************************************************/ + + Matrix.prototype.toString = function() { + var out = []; + var rule, type, switchName, val; + var srcHostname, desHostname; + for ( rule of this.rules.keys() ) { + srcHostname = this.srcHostnameFromRule(rule); + desHostname = this.desHostnameFromRule(rule); + for ( type of typeBitOffsets.keys() ) { + val = this.evaluateCell(srcHostname, desHostname, type); + if ( val === 0 ) { continue; } + out.push( + punycode.toUnicode(srcHostname) + ' ' + + punycode.toUnicode(desHostname) + ' ' + + type + ' ' + + stateToNameMap.get(val) + ); + } + } + for ( srcHostname of this.switches.keys() ) { + for ( switchName of switchBitOffsets.keys() ) { + val = this.evaluateSwitch(switchName, srcHostname); + if ( val === 0 ) { continue; } + out.push(switchName + ': ' + srcHostname + ' ' + switchStateToNameMap.get(val)); } - matrixId = this.id; - readTime = this.modifiedTime; } - return cachedResult; + return out.sort().join('\n'); }; -})(); - -/******************************************************************************/ -Matrix.prototype.toString = function() { - var out = []; - var rule, type, switchName, val; - var srcHostname, desHostname; - for ( rule of this.rules.keys() ) { - srcHostname = this.srcHostnameFromRule(rule); - desHostname = this.desHostnameFromRule(rule); - for ( type of typeBitOffsets.keys() ) { - val = this.evaluateCell(srcHostname, desHostname, type); - if ( val === 0 ) { continue; } - out.push( - punycode.toUnicode(srcHostname) + ' ' + - punycode.toUnicode(desHostname) + ' ' + - type + ' ' + - stateToNameMap.get(val) - ); - } - } - for ( srcHostname of this.switches.keys() ) { - for ( switchName of switchBitOffsets.keys() ) { - val = this.evaluateSwitch(switchName, srcHostname); - if ( val === 0 ) { continue; } - out.push(switchName + ': ' + srcHostname + ' ' + switchStateToNameMap.get(val)); - } - } - return out.sort().join('\n'); -}; - -/******************************************************************************/ - -Matrix.prototype.fromString = function(text, append) { - var matrix = append ? this : new Matrix(); - var textEnd = text.length; - var lineBeg = 0, lineEnd; - var line, pos; - var fields, fieldVal; - var switchName; - var srcHostname = ''; - var desHostname = ''; - var type, state; - - while ( lineBeg < textEnd ) { - lineEnd = text.indexOf('\n', lineBeg); - if ( lineEnd < 0 ) { - lineEnd = text.indexOf('\r', lineBeg); + /******************************************************************************/ + + Matrix.prototype.fromString = function(text, append) { + var matrix = append ? this : new Matrix(); + var textEnd = text.length; + var lineBeg = 0, lineEnd; + var line, pos; + var fields, fieldVal; + var switchName; + var srcHostname = ''; + var desHostname = ''; + var type, state; + + while ( lineBeg < textEnd ) { + lineEnd = text.indexOf('\n', lineBeg); if ( lineEnd < 0 ) { - lineEnd = textEnd; + lineEnd = text.indexOf('\r', lineBeg); + if ( lineEnd < 0 ) { + lineEnd = textEnd; + } } - } - line = text.slice(lineBeg, lineEnd).trim(); - lineBeg = lineEnd + 1; - - pos = line.indexOf('# '); - if ( pos !== -1 ) { - line = line.slice(0, pos).trim(); - } - if ( line === '' ) { - continue; - } + line = text.slice(lineBeg, lineEnd).trim(); + lineBeg = lineEnd + 1; - fields = line.split(/\s+/); + pos = line.indexOf('# '); + if ( pos !== -1 ) { + line = line.slice(0, pos).trim(); + } + if ( line === '' ) { + continue; + } - // Less than 2 fields makes no sense - if ( fields.length < 2 ) { - continue; - } + fields = line.split(/\s+/); - fieldVal = fields[0]; + // Less than 2 fields makes no sense + if ( fields.length < 2 ) { + continue; + } - // Special directives: + fieldVal = fields[0]; - // title - pos = fieldVal.indexOf('title:'); - if ( pos !== -1 ) { - // TODO - continue; - } + // Special directives: - // Name - pos = fieldVal.indexOf('name:'); - if ( pos !== -1 ) { - // TODO - continue; - } + // title + pos = fieldVal.indexOf('title:'); + if ( pos !== -1 ) { + // TODO + continue; + } - // Switch on/off + // Name + pos = fieldVal.indexOf('name:'); + if ( pos !== -1 ) { + // TODO + continue; + } - // `switch:` srcHostname state - // state = [`true`, `false`] - switchName = ''; - if ( fieldVal === 'switch:' || fieldVal === 'matrix:' ) { - fieldVal = 'matrix-off:'; - } - pos = fieldVal.indexOf(':'); - if ( pos !== -1 ) { - switchName = fieldVal.slice(0, pos); - } - if ( switchBitOffsets.has(switchName) ) { - srcHostname = punycode.toASCII(fields[1]); + // Switch on/off - // No state field: reject - fieldVal = fields[2]; - if ( fieldVal === null ) { - continue; + // `switch:` srcHostname state + // state = [`true`, `false`] + switchName = ''; + if ( fieldVal === 'switch:' || fieldVal === 'matrix:' ) { + fieldVal = 'matrix-off:'; + } + pos = fieldVal.indexOf(':'); + if ( pos !== -1 ) { + switchName = fieldVal.slice(0, pos); } - // Unknown state: reject - if ( nameToSwitchStateMap.hasOwnProperty(fieldVal) === false ) { + if ( switchBitOffsets.has(switchName) ) { + srcHostname = punycode.toASCII(fields[1]); + + // No state field: reject + fieldVal = fields[2]; + if ( fieldVal === null ) { + continue; + } + // Unknown state: reject + if ( nameToSwitchStateMap.hasOwnProperty(fieldVal) === false ) { + continue; + } + + // Backward compatibility: + // `chromium-behind-the-scene` is now `behind-the-scene` + if ( srcHostname === 'chromium-behind-the-scene' ) { + srcHostname = 'behind-the-scene'; + } + + matrix.setSwitch(switchName, srcHostname, nameToSwitchStateMap[fieldVal]); continue; } - // Backward compatibility: - // `chromium-behind-the-scene` is now `behind-the-scene` - if ( srcHostname === 'chromium-behind-the-scene' ) { - srcHostname = 'behind-the-scene'; + // Unknown directive + if ( fieldVal.endsWith(':') ) { + continue; } - matrix.setSwitch(switchName, srcHostname, nameToSwitchStateMap[fieldVal]); - continue; - } - - // Unknown directive - if ( fieldVal.endsWith(':') ) { - continue; - } + // Valid rule syntax: - // Valid rule syntax: + // srcHostname desHostname [type [state]] + // type = a valid request type + // state = [`block`, `allow`, `inherit`] - // srcHostname desHostname [type [state]] - // type = a valid request type - // state = [`block`, `allow`, `inherit`] + // srcHostname desHostname type + // type = a valid request type + // state = `allow` - // srcHostname desHostname type - // type = a valid request type - // state = `allow` + // srcHostname desHostname + // type = `*` + // state = `allow` - // srcHostname desHostname - // type = `*` - // state = `allow` + // Lines with invalid syntax silently ignored - // Lines with invalid syntax silently ignored + srcHostname = punycode.toASCII(fields[0]); + desHostname = punycode.toASCII(fields[1]); - srcHostname = punycode.toASCII(fields[0]); - desHostname = punycode.toASCII(fields[1]); - - fieldVal = fields[2]; + fieldVal = fields[2]; - if ( fieldVal !== undefined ) { - type = fieldVal; - // https://github.com/gorhill/uMatrix/issues/759 - // Backward compatibility. - if ( type === 'plugin' ) { - type = 'media'; - } - // Unknown type: reject - if ( typeBitOffsets.has(type) === false ) { - continue; + if ( fieldVal !== undefined ) { + type = fieldVal; + // https://github.com/gorhill/uMatrix/issues/759 + // Backward compatibility. + if ( type === 'plugin' ) { + type = 'media'; + } + // Unknown type: reject + if ( typeBitOffsets.has(type) === false ) { + continue; + } + } else { + type = '*'; } - } else { - type = '*'; - } - fieldVal = fields[3]; + fieldVal = fields[3]; - if ( fieldVal !== undefined ) { - // Unknown state: reject - if ( nameToStateMap.hasOwnProperty(fieldVal) === false ) { - continue; + if ( fieldVal !== undefined ) { + // Unknown state: reject + if ( nameToStateMap.hasOwnProperty(fieldVal) === false ) { + continue; + } + state = nameToStateMap[fieldVal]; + } else { + state = 2; } - state = nameToStateMap[fieldVal]; - } else { - state = 2; - } - matrix.setCell(srcHostname, desHostname, type, state); - } + matrix.setCell(srcHostname, desHostname, type, state); + } - if ( !append ) { - this.assign(matrix); - } + if ( !append ) { + this.assign(matrix); + } - this.modifiedTime = Date.now(); -}; + this.modifiedTime = Date.now(); + }; -/******************************************************************************/ + /******************************************************************************/ -Matrix.prototype.toSelfie = function() { - return { - magicId: magicId, - switches: Array.from(this.switches), - rules: Array.from(this.rules) + Matrix.prototype.toSelfie = function() { + return { + magicId: magicId, + switches: Array.from(this.switches), + rules: Array.from(this.rules) + }; }; -}; -/******************************************************************************/ + /******************************************************************************/ -Matrix.prototype.fromSelfie = function(selfie) { - if ( selfie.magicId !== magicId ) { return false; } - this.switches = new Map(selfie.switches); - this.rules = new Map(selfie.rules); - this.modifiedTime = Date.now(); - return true; -}; + Matrix.prototype.fromSelfie = function(selfie) { + if ( selfie.magicId !== magicId ) { return false; } + this.switches = new Map(selfie.switches); + this.rules = new Map(selfie.rules); + this.modifiedTime = Date.now(); + return true; + }; -/******************************************************************************/ + /******************************************************************************/ -Matrix.prototype.diff = function(other, srcHostname, desHostnames) { - var out = []; - var desHostname, type; - var switchName, i, thisVal, otherVal; - for (;;) { - for ( switchName of switchBitOffsets.keys() ) { - thisVal = this.evaluateSwitch(switchName, srcHostname); - otherVal = other.evaluateSwitch(switchName, srcHostname); - if ( thisVal !== otherVal ) { - out.push({ - 'what': switchName, - 'src': srcHostname - }); + Matrix.prototype.diff = function(other, srcHostname, desHostnames) { + var out = []; + var desHostname, type; + var switchName, i, thisVal, otherVal; + for (;;) { + for ( switchName of switchBitOffsets.keys() ) { + thisVal = this.evaluateSwitch(switchName, srcHostname); + otherVal = other.evaluateSwitch(switchName, srcHostname); + if ( thisVal !== otherVal ) { + out.push({ + 'what': switchName, + 'src': srcHostname + }); + } } - } - i = desHostnames.length; - while ( i-- ) { - desHostname = desHostnames[i]; - for ( type of typeBitOffsets.keys() ) { - thisVal = this.evaluateCell(srcHostname, desHostname, type); - otherVal = other.evaluateCell(srcHostname, desHostname, type); - if ( thisVal === otherVal ) { continue; } - out.push({ - 'what': 'rule', - 'src': srcHostname, - 'des': desHostname, - 'type': type - }); + i = desHostnames.length; + while ( i-- ) { + desHostname = desHostnames[i]; + for ( type of typeBitOffsets.keys() ) { + thisVal = this.evaluateCell(srcHostname, desHostname, type); + otherVal = other.evaluateCell(srcHostname, desHostname, type); + if ( thisVal === otherVal ) { continue; } + out.push({ + 'what': 'rule', + 'src': srcHostname, + 'des': desHostname, + 'type': type + }); + } + } + srcHostname = toBroaderHostname(srcHostname); + if ( srcHostname === '' ) { + break; } } - srcHostname = toBroaderHostname(srcHostname); - if ( srcHostname === '' ) { - break; - } - } - return out; -}; + return out; + }; -/******************************************************************************/ + /******************************************************************************/ -Matrix.prototype.applyDiff = function(diff, from) { - var changed = false; - var i = diff.length; - var action, val; - while ( i-- ) { - action = diff[i]; - if ( action.what === 'rule' ) { - val = from.evaluateCell(action.src, action.des, action.type); - changed = this.setCell(action.src, action.des, action.type, val) || changed; - continue; - } - if ( switchBitOffsets.has(action.what) ) { - val = from.evaluateSwitch(action.what, action.src); - changed = this.setSwitch(action.what, action.src, val) || changed; - continue; - } - } - return changed; -}; + Matrix.prototype.applyDiff = function(diff, from) { + var changed = false; + var i = diff.length; + var action, val; + while ( i-- ) { + action = diff[i]; + if ( action.what === 'rule' ) { + val = from.evaluateCell(action.src, action.des, action.type); + changed = this.setCell(action.src, action.des, action.type, val) || changed; + continue; + } + if ( switchBitOffsets.has(action.what) ) { + val = from.evaluateSwitch(action.what, action.src); + changed = this.setSwitch(action.what, action.src, val) || changed; + continue; + } + } + return changed; + }; -/******************************************************************************/ + /******************************************************************************/ -return Matrix; + return Matrix; -/******************************************************************************/ + /******************************************************************************/ -// https://www.youtube.com/watch?v=wlNrQGmj6oQ + // https://www.youtube.com/watch?v=wlNrQGmj6oQ })(); diff --git a/js/messaging.js b/js/messaging.js index 45d4f70..3252872 100644 --- a/js/messaging.js +++ b/js/messaging.js @@ -30,105 +30,105 @@ (function() { -var ηm = ηMatrix; + var ηm = ηMatrix; -/******************************************************************************/ + /******************************************************************************/ -// Default is for commonly used message. + // Default is for commonly used message. -function onMessage(request, sender, callback) { - // Async - switch ( request.what ) { - case 'getAssetContent': - ηm.assets.get(request.url, { dontCache: true }, callback); - return; + function onMessage(request, sender, callback) { + // Async + switch ( request.what ) { + case 'getAssetContent': + ηm.assets.get(request.url, { dontCache: true }, callback); + return; - case 'selectHostsFiles': - ηm.selectHostsFiles(request, callback); - return; + case 'selectHostsFiles': + ηm.selectHostsFiles(request, callback); + return; - default: - break; - } + default: + break; + } - // Sync - var response; - - switch ( request.what ) { - case 'forceReloadTab': - ηm.forceReload(request.tabId, request.bypassCache); - break; - - case 'forceUpdateAssets': - ηm.scheduleAssetUpdater(0); - ηm.assets.updateStart({ delay: 2000 }); - break; - - case 'getUserSettings': - response = { - userSettings: ηm.userSettings, - matrixSwitches: { - 'https-strict': ηm.pMatrix.evaluateSwitch('https-strict', '*') === 1, - 'referrer-spoof': ηm.pMatrix.evaluateSwitch('referrer-spoof', '*') === 1, - 'noscript-spoof': ηm.pMatrix.evaluateSwitch('noscript-spoof', '*') === 1 - } - }; - break; + // Sync + var response; - case 'gotoExtensionURL': - ηm.gotoExtensionURL(request); - break; + switch ( request.what ) { + case 'forceReloadTab': + ηm.forceReload(request.tabId, request.bypassCache); + break; - case 'gotoURL': - ηm.gotoURL(request); - break; + case 'forceUpdateAssets': + ηm.scheduleAssetUpdater(0); + ηm.assets.updateStart({ delay: 2000 }); + break; - case 'mustBlock': - response = ηm.mustBlock( - request.scope, - request.hostname, - request.type - ); - break; + case 'getUserSettings': + response = { + userSettings: ηm.userSettings, + matrixSwitches: { + 'https-strict': ηm.pMatrix.evaluateSwitch('https-strict', '*') === 1, + 'referrer-spoof': ηm.pMatrix.evaluateSwitch('referrer-spoof', '*') === 1, + 'noscript-spoof': ηm.pMatrix.evaluateSwitch('noscript-spoof', '*') === 1 + } + }; + break; - case 'readRawSettings': - response = ηm.stringFromRawSettings(); - break; + case 'gotoExtensionURL': + ηm.gotoExtensionURL(request); + break; - case 'reloadHostsFiles': - ηm.reloadHostsFiles(); - break; + case 'gotoURL': + ηm.gotoURL(request); + break; - case 'setMatrixSwitch': - ηm.tMatrix.setSwitch(request.switchName, '*', request.state); - if ( ηm.pMatrix.setSwitch(request.switchName, '*', request.state) ) { - ηm.saveMatrix(); - } - break; + case 'mustBlock': + response = ηm.mustBlock( + request.scope, + request.hostname, + request.type + ); + break; - case 'userSettings': - if ( request.hasOwnProperty('value') === false ) { - request.value = undefined; - } - response = ηm.changeUserSettings(request.name, request.value); - break; + case 'readRawSettings': + response = ηm.stringFromRawSettings(); + break; + + case 'reloadHostsFiles': + ηm.reloadHostsFiles(); + break; - case 'writeRawSettings': - ηm.rawSettingsFromString(request.content); - break; + case 'setMatrixSwitch': + ηm.tMatrix.setSwitch(request.switchName, '*', request.state); + if ( ηm.pMatrix.setSwitch(request.switchName, '*', request.state) ) { + ηm.saveMatrix(); + } + break; - default: - return vAPI.messaging.UNHANDLED; - } + case 'userSettings': + if ( request.hasOwnProperty('value') === false ) { + request.value = undefined; + } + response = ηm.changeUserSettings(request.name, request.value); + break; - callback(response); -} + case 'writeRawSettings': + ηm.rawSettingsFromString(request.content); + break; -/******************************************************************************/ + default: + return vAPI.messaging.UNHANDLED; + } -vAPI.messaging.setup(onMessage); + callback(response); + } -/******************************************************************************/ + /******************************************************************************/ + + vAPI.messaging.setup(onMessage); + + /******************************************************************************/ })(); @@ -137,256 +137,256 @@ vAPI.messaging.setup(onMessage); (function() { -// popup.js + // popup.js -var ηm = ηMatrix; - -/******************************************************************************/ + var ηm = ηMatrix; -// Constructor is faster than object literal + /******************************************************************************/ -var RowSnapshot = function(srcHostname, desHostname, desDomain) { - this.domain = desDomain; - this.temporary = ηm.tMatrix.evaluateRowZXY(srcHostname, desHostname); - this.permanent = ηm.pMatrix.evaluateRowZXY(srcHostname, desHostname); - this.counts = RowSnapshot.counts.slice(); - this.totals = RowSnapshot.counts.slice(); -}; + // Constructor is faster than object literal -RowSnapshot.counts = (function() { - var aa = []; - for ( var i = 0, n = ηm.Matrix.columnHeaderIndices.size; i < n; i++ ) { - aa[i] = 0; - } - return aa; -})(); + var RowSnapshot = function(srcHostname, desHostname, desDomain) { + this.domain = desDomain; + this.temporary = ηm.tMatrix.evaluateRowZXY(srcHostname, desHostname); + this.permanent = ηm.pMatrix.evaluateRowZXY(srcHostname, desHostname); + this.counts = RowSnapshot.counts.slice(); + this.totals = RowSnapshot.counts.slice(); + }; -/******************************************************************************/ + RowSnapshot.counts = (function() { + var aa = []; + for ( var i = 0, n = ηm.Matrix.columnHeaderIndices.size; i < n; i++ ) { + aa[i] = 0; + } + return aa; + })(); + + /******************************************************************************/ + + var matrixSnapshot = function(pageStore, details) { + var ηmuser = ηm.userSettings; + var headerIndices = ηm.Matrix.columnHeaderIndices; + + var r = { + appVersion: vAPI.app.version, + blockedCount: pageStore.requestStats.blocked.all, + collapseAllDomains: ηmuser.popupCollapseAllDomains, + collapseBlacklistedDomains: ηmuser.popupCollapseBlacklistedDomains, + diff: [], + domain: pageStore.pageDomain, + has3pReferrer: pageStore.has3pReferrer, + hasMixedContent: pageStore.hasMixedContent, + hasNoscriptTags: pageStore.hasNoscriptTags, + hasWebWorkers: pageStore.hasWebWorkers, + headerIndices: Array.from(headerIndices), + hostname: pageStore.pageHostname, + mtxContentModified: pageStore.mtxContentModifiedTime !== details.mtxContentModifiedTime, + mtxCountModified: pageStore.mtxCountModifiedTime !== details.mtxCountModifiedTime, + mtxContentModifiedTime: pageStore.mtxContentModifiedTime, + mtxCountModifiedTime: pageStore.mtxCountModifiedTime, + pMatrixModified: ηm.pMatrix.modifiedTime !== details.pMatrixModifiedTime, + pMatrixModifiedTime: ηm.pMatrix.modifiedTime, + pSwitches: {}, + rows: {}, + rowCount: 0, + scope: '*', + tabId: pageStore.tabId, + tMatrixModified: ηm.tMatrix.modifiedTime !== details.tMatrixModifiedTime, + tMatrixModifiedTime: ηm.tMatrix.modifiedTime, + tSwitches: {}, + url: pageStore.pageUrl, + userSettings: { + colorBlindFriendly: ηmuser.colorBlindFriendly, + displayTextSize: ηmuser.displayTextSize, + popupScopeLevel: ηmuser.popupScopeLevel + } + }; -var matrixSnapshot = function(pageStore, details) { - var ηmuser = ηm.userSettings; - var headerIndices = ηm.Matrix.columnHeaderIndices; - - var r = { - appVersion: vAPI.app.version, - blockedCount: pageStore.requestStats.blocked.all, - collapseAllDomains: ηmuser.popupCollapseAllDomains, - collapseBlacklistedDomains: ηmuser.popupCollapseBlacklistedDomains, - diff: [], - domain: pageStore.pageDomain, - has3pReferrer: pageStore.has3pReferrer, - hasMixedContent: pageStore.hasMixedContent, - hasNoscriptTags: pageStore.hasNoscriptTags, - hasWebWorkers: pageStore.hasWebWorkers, - headerIndices: Array.from(headerIndices), - hostname: pageStore.pageHostname, - mtxContentModified: pageStore.mtxContentModifiedTime !== details.mtxContentModifiedTime, - mtxCountModified: pageStore.mtxCountModifiedTime !== details.mtxCountModifiedTime, - mtxContentModifiedTime: pageStore.mtxContentModifiedTime, - mtxCountModifiedTime: pageStore.mtxCountModifiedTime, - pMatrixModified: ηm.pMatrix.modifiedTime !== details.pMatrixModifiedTime, - pMatrixModifiedTime: ηm.pMatrix.modifiedTime, - pSwitches: {}, - rows: {}, - rowCount: 0, - scope: '*', - tabId: pageStore.tabId, - tMatrixModified: ηm.tMatrix.modifiedTime !== details.tMatrixModifiedTime, - tMatrixModifiedTime: ηm.tMatrix.modifiedTime, - tSwitches: {}, - url: pageStore.pageUrl, - userSettings: { - colorBlindFriendly: ηmuser.colorBlindFriendly, - displayTextSize: ηmuser.displayTextSize, - popupScopeLevel: ηmuser.popupScopeLevel + if ( typeof details.scope === 'string' ) { + r.scope = details.scope; + } else if ( ηmuser.popupScopeLevel === 'site' ) { + r.scope = r.hostname; + } else if ( ηmuser.popupScopeLevel === 'domain' ) { + r.scope = r.domain; } - }; - if ( typeof details.scope === 'string' ) { - r.scope = details.scope; - } else if ( ηmuser.popupScopeLevel === 'site' ) { - r.scope = r.hostname; - } else if ( ηmuser.popupScopeLevel === 'domain' ) { - r.scope = r.domain; - } + for ( var switchName of ηm.Matrix.switchNames ) { + r.tSwitches[switchName] = ηm.tMatrix.evaluateSwitchZ(switchName, r.scope); + r.pSwitches[switchName] = ηm.pMatrix.evaluateSwitchZ(switchName, r.scope); + } - for ( var switchName of ηm.Matrix.switchNames ) { - r.tSwitches[switchName] = ηm.tMatrix.evaluateSwitchZ(switchName, r.scope); - r.pSwitches[switchName] = ηm.pMatrix.evaluateSwitchZ(switchName, r.scope); - } + // These rows always exist + r.rows['*'] = new RowSnapshot(r.scope, '*', '*'); + r.rows['1st-party'] = new RowSnapshot(r.scope, '1st-party', '1st-party'); + r.rowCount += 1; + + var ηmuri = ηm.URI; + var reqType, reqHostname, reqDomain; + var desHostname; + var row, typeIndex; + var anyIndex = headerIndices.get('*'); + var pos, count; + + for ( var entry of pageStore.hostnameTypeCells ) { + pos = entry[0].indexOf(' '); + reqHostname = entry[0].slice(0, pos); + reqType = entry[0].slice(pos + 1); + // rhill 2013-10-23: hostname can be empty if the request is a data url + // https://github.com/gorhill/httpswitchboard/issues/26 + if ( reqHostname === '' ) { + reqHostname = pageStore.pageHostname; + } + reqDomain = ηmuri.domainFromHostname(reqHostname) || reqHostname; + + // We want rows of self and ancestors + desHostname = reqHostname; + for (;;) { + // If row exists, ancestors exist + if ( r.rows.hasOwnProperty(desHostname) !== false ) { break; } + r.rows[desHostname] = new RowSnapshot(r.scope, desHostname, reqDomain); + r.rowCount += 1; + if ( desHostname === reqDomain ) { break; } + pos = desHostname.indexOf('.'); + if ( pos === -1 ) { break; } + desHostname = desHostname.slice(pos + 1); + } - // These rows always exist - r.rows['*'] = new RowSnapshot(r.scope, '*', '*'); - r.rows['1st-party'] = new RowSnapshot(r.scope, '1st-party', '1st-party'); - r.rowCount += 1; - - var ηmuri = ηm.URI; - var reqType, reqHostname, reqDomain; - var desHostname; - var row, typeIndex; - var anyIndex = headerIndices.get('*'); - var pos, count; - - for ( var entry of pageStore.hostnameTypeCells ) { - pos = entry[0].indexOf(' '); - reqHostname = entry[0].slice(0, pos); - reqType = entry[0].slice(pos + 1); - // rhill 2013-10-23: hostname can be empty if the request is a data url - // https://github.com/gorhill/httpswitchboard/issues/26 - if ( reqHostname === '' ) { - reqHostname = pageStore.pageHostname; - } - reqDomain = ηmuri.domainFromHostname(reqHostname) || reqHostname; - - // We want rows of self and ancestors - desHostname = reqHostname; - for (;;) { - // If row exists, ancestors exist - if ( r.rows.hasOwnProperty(desHostname) !== false ) { break; } - r.rows[desHostname] = new RowSnapshot(r.scope, desHostname, reqDomain); - r.rowCount += 1; - if ( desHostname === reqDomain ) { break; } - pos = desHostname.indexOf('.'); - if ( pos === -1 ) { break; } - desHostname = desHostname.slice(pos + 1); + count = entry[1].size; + typeIndex = headerIndices.get(reqType); + row = r.rows[reqHostname]; + row.counts[typeIndex] += count; + row.counts[anyIndex] += count; + row = r.rows[reqDomain]; + row.totals[typeIndex] += count; + row.totals[anyIndex] += count; + row = r.rows['*']; + row.totals[typeIndex] += count; + row.totals[anyIndex] += count; } - count = entry[1].size; - typeIndex = headerIndices.get(reqType); - row = r.rows[reqHostname]; - row.counts[typeIndex] += count; - row.counts[anyIndex] += count; - row = r.rows[reqDomain]; - row.totals[typeIndex] += count; - row.totals[anyIndex] += count; - row = r.rows['*']; - row.totals[typeIndex] += count; - row.totals[anyIndex] += count; - } + r.diff = ηm.tMatrix.diff(ηm.pMatrix, r.hostname, Object.keys(r.rows)); - r.diff = ηm.tMatrix.diff(ηm.pMatrix, r.hostname, Object.keys(r.rows)); + return r; + }; - return r; -}; + /******************************************************************************/ -/******************************************************************************/ + var matrixSnapshotFromTabId = function(details, callback) { + var matrixSnapshotIf = function(tabId, details) { + var pageStore = ηm.pageStoreFromTabId(tabId); + if ( pageStore === null ) { + callback('ENOTFOUND'); + return; + } -var matrixSnapshotFromTabId = function(details, callback) { - var matrixSnapshotIf = function(tabId, details) { - var pageStore = ηm.pageStoreFromTabId(tabId); - if ( pageStore === null ) { - callback('ENOTFOUND'); + // First verify whether we must return data or not. + if ( + ηm.tMatrix.modifiedTime === details.tMatrixModifiedTime && + ηm.pMatrix.modifiedTime === details.pMatrixModifiedTime && + pageStore.mtxContentModifiedTime === details.mtxContentModifiedTime && + pageStore.mtxCountModifiedTime === details.mtxCountModifiedTime + ) { + callback('ENOCHANGE'); + return ; + } + + callback(matrixSnapshot(pageStore, details)); + }; + + // Specific tab id requested? + if ( details.tabId ) { + matrixSnapshotIf(details.tabId, details); return; } - // First verify whether we must return data or not. - if ( - ηm.tMatrix.modifiedTime === details.tMatrixModifiedTime && - ηm.pMatrix.modifiedTime === details.pMatrixModifiedTime && - pageStore.mtxContentModifiedTime === details.mtxContentModifiedTime && - pageStore.mtxCountModifiedTime === details.mtxCountModifiedTime - ) { - callback('ENOCHANGE'); - return ; - } + // Fall back to currently active tab + var onTabReady = function(tab) { + if ( tab instanceof Object === false ) { + callback('ENOTFOUND'); + return; + } + + // Allow examination of behind-the-scene requests + var tabId = tab.url.lastIndexOf(vAPI.getURL('dashboard.html'), 0) !== 0 ? + tab.id : + vAPI.noTabId; + matrixSnapshotIf(tabId, details); + }; - callback(matrixSnapshot(pageStore, details)); + vAPI.tabs.get(null, onTabReady); }; - // Specific tab id requested? - if ( details.tabId ) { - matrixSnapshotIf(details.tabId, details); - return; - } + /******************************************************************************/ - // Fall back to currently active tab - var onTabReady = function(tab) { - if ( tab instanceof Object === false ) { - callback('ENOTFOUND'); + var onMessage = function(request, sender, callback) { + // Async + switch ( request.what ) { + case 'matrixSnapshot': + matrixSnapshotFromTabId(request, callback); return; - } - - // Allow examination of behind-the-scene requests - var tabId = tab.url.lastIndexOf(vAPI.getURL('dashboard.html'), 0) !== 0 ? - tab.id : - vAPI.noTabId; - matrixSnapshotIf(tabId, details); - }; - vAPI.tabs.get(null, onTabReady); -}; + default: + break; + } -/******************************************************************************/ + // Sync + var response; -var onMessage = function(request, sender, callback) { - // Async - switch ( request.what ) { - case 'matrixSnapshot': - matrixSnapshotFromTabId(request, callback); - return; + switch ( request.what ) { + case 'toggleMatrixSwitch': + ηm.tMatrix.setSwitchZ( + request.switchName, + request.srcHostname, + ηm.tMatrix.evaluateSwitchZ(request.switchName, request.srcHostname) === false + ); + break; - default: - break; - } + case 'blacklistMatrixCell': + ηm.tMatrix.blacklistCell( + request.srcHostname, + request.desHostname, + request.type + ); + break; - // Sync - var response; + case 'whitelistMatrixCell': + ηm.tMatrix.whitelistCell( + request.srcHostname, + request.desHostname, + request.type + ); + break; - switch ( request.what ) { - case 'toggleMatrixSwitch': - ηm.tMatrix.setSwitchZ( - request.switchName, - request.srcHostname, - ηm.tMatrix.evaluateSwitchZ(request.switchName, request.srcHostname) === false - ); - break; + case 'graylistMatrixCell': + ηm.tMatrix.graylistCell( + request.srcHostname, + request.desHostname, + request.type + ); + break; - case 'blacklistMatrixCell': - ηm.tMatrix.blacklistCell( - request.srcHostname, - request.desHostname, - request.type - ); - break; + case 'applyDiffToPermanentMatrix': // aka "persist" + if ( ηm.pMatrix.applyDiff(request.diff, ηm.tMatrix) ) { + ηm.saveMatrix(); + } + break; - case 'whitelistMatrixCell': - ηm.tMatrix.whitelistCell( - request.srcHostname, - request.desHostname, - request.type - ); - break; + case 'applyDiffToTemporaryMatrix': // aka "revert" + ηm.tMatrix.applyDiff(request.diff, ηm.pMatrix); + break; - case 'graylistMatrixCell': - ηm.tMatrix.graylistCell( - request.srcHostname, - request.desHostname, - request.type - ); - break; + case 'revertTemporaryMatrix': + ηm.tMatrix.assign(ηm.pMatrix); + break; - case 'applyDiffToPermanentMatrix': // aka "persist" - if ( ηm.pMatrix.applyDiff(request.diff, ηm.tMatrix) ) { - ηm.saveMatrix(); + default: + return vAPI.messaging.UNHANDLED; } - break; - - case 'applyDiffToTemporaryMatrix': // aka "revert" - ηm.tMatrix.applyDiff(request.diff, ηm.pMatrix); - break; - - case 'revertTemporaryMatrix': - ηm.tMatrix.assign(ηm.pMatrix); - break; - default: - return vAPI.messaging.UNHANDLED; - } - - callback(response); -}; + callback(response); + }; -vAPI.messaging.listen('popup.js', onMessage); + vAPI.messaging.listen('popup.js', onMessage); })(); @@ -397,195 +397,195 @@ vAPI.messaging.listen('popup.js', onMessage); (function() { -var ηm = ηMatrix; + var ηm = ηMatrix; -/******************************************************************************/ + /******************************************************************************/ -var foundInlineCode = function(tabId, pageStore, details, type) { - if ( pageStore === null ) { return; } + var foundInlineCode = function(tabId, pageStore, details, type) { + if ( pageStore === null ) { return; } - var pageHostname = pageStore.pageHostname, - ηmuri = ηm.URI.set(details.documentURI), - frameURL = ηmuri.normalizedURI(); + var pageHostname = pageStore.pageHostname, + ηmuri = ηm.URI.set(details.documentURI), + frameURL = ηmuri.normalizedURI(); - var blocked = details.blocked; - if ( blocked === undefined ) { - blocked = ηm.mustBlock(pageHostname, ηmuri.hostname, type); - } + var blocked = details.blocked; + if ( blocked === undefined ) { + blocked = ηm.mustBlock(pageHostname, ηmuri.hostname, type); + } - var mapTo = { - css: 'style', - script: 'script' + var mapTo = { + css: 'style', + script: 'script' + }; + + // https://github.com/gorhill/httpswitchboard/issues/333 + // Look-up here whether inline scripting is blocked for the frame. + var url = frameURL + '{inline_' + mapTo[type] + '}'; + pageStore.recordRequest(type, url, blocked); + ηm.logger.writeOne(tabId, 'net', pageHostname, url, type, blocked); }; - // https://github.com/gorhill/httpswitchboard/issues/333 - // Look-up here whether inline scripting is blocked for the frame. - var url = frameURL + '{inline_' + mapTo[type] + '}'; - pageStore.recordRequest(type, url, blocked); - ηm.logger.writeOne(tabId, 'net', pageHostname, url, type, blocked); -}; + /******************************************************************************/ -/******************************************************************************/ + var contentScriptLocalStorageHandler = function(tabId, originURL) { + var tabContext = ηm.tabContextManager.lookup(tabId); + if ( tabContext === null ) { return; } -var contentScriptLocalStorageHandler = function(tabId, originURL) { - var tabContext = ηm.tabContextManager.lookup(tabId); - if ( tabContext === null ) { return; } - - var blocked = ηm.mustBlock( - tabContext.rootHostname, - ηm.URI.hostnameFromURI(originURL), - 'cookie' - ); - - var pageStore = ηm.pageStoreFromTabId(tabId); - if ( pageStore !== null ) { - var requestURL = originURL + '/{localStorage}'; - pageStore.recordRequest('cookie', requestURL, blocked); - ηm.logger.writeOne(tabId, 'net', tabContext.rootHostname, requestURL, 'cookie', blocked); - } + var blocked = ηm.mustBlock( + tabContext.rootHostname, + ηm.URI.hostnameFromURI(originURL), + 'cookie' + ); - var removeStorage = blocked && ηm.userSettings.deleteLocalStorage; - if ( removeStorage ) { - ηm.localStorageRemovedCounter++; - } + var pageStore = ηm.pageStoreFromTabId(tabId); + if ( pageStore !== null ) { + var requestURL = originURL + '/{localStorage}'; + pageStore.recordRequest('cookie', requestURL, blocked); + ηm.logger.writeOne(tabId, 'net', tabContext.rootHostname, requestURL, 'cookie', blocked); + } - return removeStorage; -}; + var removeStorage = blocked && ηm.userSettings.deleteLocalStorage; + if ( removeStorage ) { + ηm.localStorageRemovedCounter++; + } -/******************************************************************************/ + return removeStorage; + }; -// Evaluate many URLs against the matrix. + /******************************************************************************/ -var lookupBlockedCollapsibles = function(tabId, requests) { - if ( placeholdersReadTime < ηm.rawSettingsWriteTime ) { - placeholders = undefined; - } + // Evaluate many URLs against the matrix. - if ( placeholders === undefined ) { - placeholders = { - frame: ηm.rawSettings.framePlaceholder, - image: ηm.rawSettings.imagePlaceholder - }; - if ( placeholders.frame ) { - placeholders.frameDocument = - ηm.rawSettings.framePlaceholderDocument.replace( - '{{bg}}', - ηm.rawSettings.framePlaceholderBackground !== 'default' ? - ηm.rawSettings.framePlaceholderBackground : - ηm.rawSettings.placeholderBackground - ); + var lookupBlockedCollapsibles = function(tabId, requests) { + if ( placeholdersReadTime < ηm.rawSettingsWriteTime ) { + placeholders = undefined; } - if ( placeholders.image ) { - placeholders.imageBorder = - ηm.rawSettings.imagePlaceholderBorder !== 'default' ? + + if ( placeholders === undefined ) { + placeholders = { + frame: ηm.rawSettings.framePlaceholder, + image: ηm.rawSettings.imagePlaceholder + }; + if ( placeholders.frame ) { + placeholders.frameDocument = + ηm.rawSettings.framePlaceholderDocument.replace( + '{{bg}}', + ηm.rawSettings.framePlaceholderBackground !== 'default' ? + ηm.rawSettings.framePlaceholderBackground : + ηm.rawSettings.placeholderBackground + ); + } + if ( placeholders.image ) { + placeholders.imageBorder = + ηm.rawSettings.imagePlaceholderBorder !== 'default' ? ηm.rawSettings.imagePlaceholderBorder : ηm.rawSettings.placeholderBorder; - placeholders.imageBackground = - ηm.rawSettings.imagePlaceholderBackground !== 'default' ? + placeholders.imageBackground = + ηm.rawSettings.imagePlaceholderBackground !== 'default' ? ηm.rawSettings.imagePlaceholderBackground : ηm.rawSettings.placeholderBackground; + } + placeholdersReadTime = Date.now(); } - placeholdersReadTime = Date.now(); - } - var response = { - blockedResources: [], - hash: requests.hash, - id: requests.id, - placeholders: placeholders - }; + var response = { + blockedResources: [], + hash: requests.hash, + id: requests.id, + placeholders: placeholders + }; - var tabContext = ηm.tabContextManager.lookup(tabId); - if ( tabContext === null ) { - return response; - } + var tabContext = ηm.tabContextManager.lookup(tabId); + if ( tabContext === null ) { + return response; + } - var pageStore = ηm.pageStoreFromTabId(tabId); - if ( pageStore !== null ) { - pageStore.lookupBlockedCollapsibles(requests, response); - } + var pageStore = ηm.pageStoreFromTabId(tabId); + if ( pageStore !== null ) { + pageStore.lookupBlockedCollapsibles(requests, response); + } - return response; -}; + return response; + }; -var placeholders, - placeholdersReadTime = 0; + var placeholders, + placeholdersReadTime = 0; -/******************************************************************************/ + /******************************************************************************/ -var onMessage = function(request, sender, callback) { - // Async - switch ( request.what ) { - default: - break; - } + var onMessage = function(request, sender, callback) { + // Async + switch ( request.what ) { + default: + break; + } - var tabId = sender && sender.tab ? sender.tab.id || 0 : 0, - tabContext = ηm.tabContextManager.lookup(tabId), - rootHostname = tabContext && tabContext.rootHostname, - pageStore = ηm.pageStoreFromTabId(tabId); + var tabId = sender && sender.tab ? sender.tab.id || 0 : 0, + tabContext = ηm.tabContextManager.lookup(tabId), + rootHostname = tabContext && tabContext.rootHostname, + pageStore = ηm.pageStoreFromTabId(tabId); - // Sync - var response; + // Sync + var response; - switch ( request.what ) { - case 'contentScriptHasLocalStorage': - response = contentScriptLocalStorageHandler(tabId, request.originURL); - break; + switch ( request.what ) { + case 'contentScriptHasLocalStorage': + response = contentScriptLocalStorageHandler(tabId, request.originURL); + break; - case 'lookupBlockedCollapsibles': - response = lookupBlockedCollapsibles(tabId, request); - break; + case 'lookupBlockedCollapsibles': + response = lookupBlockedCollapsibles(tabId, request); + break; - case 'mustRenderNoscriptTags?': - if ( tabContext === null ) { break; } - response = - ηm.tMatrix.mustBlock(rootHostname, rootHostname, 'script') && - ηm.tMatrix.evaluateSwitchZ('noscript-spoof', rootHostname); - if ( pageStore !== null ) { - pageStore.hasNoscriptTags = true; - } - // https://github.com/gorhill/uMatrix/issues/225 - // A good place to force an update of the page title, as at - // this point the DOM has been loaded. - ηm.updateTitle(tabId); - break; - - case 'securityPolicyViolation': - if ( request.directive === 'worker-src' ) { - var url = ηm.URI.hostnameFromURI(request.blockedURI) !== '' ? - request.blockedURI : - request.documentURI; + case 'mustRenderNoscriptTags?': + if ( tabContext === null ) { break; } + response = + ηm.tMatrix.mustBlock(rootHostname, rootHostname, 'script') && + ηm.tMatrix.evaluateSwitchZ('noscript-spoof', rootHostname); if ( pageStore !== null ) { - pageStore.hasWebWorkers = true; - pageStore.recordRequest('script', url, request.blocked); + pageStore.hasNoscriptTags = true; } + // https://github.com/gorhill/uMatrix/issues/225 + // A good place to force an update of the page title, as at + // this point the DOM has been loaded. + ηm.updateTitle(tabId); + break; + + case 'securityPolicyViolation': + if ( request.directive === 'worker-src' ) { + var url = ηm.URI.hostnameFromURI(request.blockedURI) !== '' ? + request.blockedURI : + request.documentURI; + if ( pageStore !== null ) { + pageStore.hasWebWorkers = true; + pageStore.recordRequest('script', url, request.blocked); + } + if ( tabContext !== null ) { + ηm.logger.writeOne(tabId, 'net', rootHostname, url, 'worker', request.blocked); + } + } else if ( request.directive === 'script-src' ) { + foundInlineCode(tabId, pageStore, request, 'script'); + } else if ( request.directive === 'style-src' ) { + foundInlineCode(tabId, pageStore, request, 'css'); + } + break; + + case 'shutdown?': if ( tabContext !== null ) { - ηm.logger.writeOne(tabId, 'net', rootHostname, url, 'worker', request.blocked); + response = ηm.tMatrix.evaluateSwitchZ('matrix-off', rootHostname); } - } else if ( request.directive === 'script-src' ) { - foundInlineCode(tabId, pageStore, request, 'script'); - } else if ( request.directive === 'style-src' ) { - foundInlineCode(tabId, pageStore, request, 'css'); - } - break; + break; - case 'shutdown?': - if ( tabContext !== null ) { - response = ηm.tMatrix.evaluateSwitchZ('matrix-off', rootHostname); + default: + return vAPI.messaging.UNHANDLED; } - break; - default: - return vAPI.messaging.UNHANDLED; - } - - callback(response); -}; + callback(response); + }; -vAPI.messaging.listen('contentscript.js', onMessage); + vAPI.messaging.listen('contentscript.js', onMessage); -/******************************************************************************/ + /******************************************************************************/ })(); @@ -596,56 +596,56 @@ vAPI.messaging.listen('contentscript.js', onMessage); (function() { -/******************************************************************************/ + /******************************************************************************/ -var ηm = ηMatrix; + var ηm = ηMatrix; -/******************************************************************************/ + /******************************************************************************/ -var onMessage = function(request, sender, callback) { - // Async - switch ( request.what ) { - case 'cloudGetOptions': - vAPI.cloud.getOptions(function(options) { - options.enabled = ηm.userSettings.cloudStorageEnabled === true; - callback(options); - }); - return; + var onMessage = function(request, sender, callback) { + // Async + switch ( request.what ) { + case 'cloudGetOptions': + vAPI.cloud.getOptions(function(options) { + options.enabled = ηm.userSettings.cloudStorageEnabled === true; + callback(options); + }); + return; - case 'cloudSetOptions': - vAPI.cloud.setOptions(request.options, callback); - return; + case 'cloudSetOptions': + vAPI.cloud.setOptions(request.options, callback); + return; - case 'cloudPull': - return vAPI.cloud.pull(request.datakey, callback); + case 'cloudPull': + return vAPI.cloud.pull(request.datakey, callback); - case 'cloudPush': - return vAPI.cloud.push(request.datakey, request.data, callback); + case 'cloudPush': + return vAPI.cloud.push(request.datakey, request.data, callback); - default: - break; - } + default: + break; + } - // Sync - var response; + // Sync + var response; - switch ( request.what ) { - // For when cloud storage is disabled. - case 'cloudPull': - // fallthrough - case 'cloudPush': - break; + switch ( request.what ) { + // For when cloud storage is disabled. + case 'cloudPull': + // fallthrough + case 'cloudPush': + break; - default: - return vAPI.messaging.UNHANDLED; - } + default: + return vAPI.messaging.UNHANDLED; + } - callback(response); -}; + callback(response); + }; -/******************************************************************************/ + /******************************************************************************/ -vAPI.messaging.listen('cloud-ui.js', onMessage); + vAPI.messaging.listen('cloud-ui.js', onMessage); })(); @@ -656,51 +656,51 @@ vAPI.messaging.listen('cloud-ui.js', onMessage); (function() { -var ηm = ηMatrix; + var ηm = ηMatrix; -/******************************************************************************/ + /******************************************************************************/ -var onMessage = function(request, sender, callback) { + var onMessage = function(request, sender, callback) { - // Async - switch ( request.what ) { - default: - break; - } + // Async + switch ( request.what ) { + default: + break; + } - // Sync - var response; + // Sync + var response; - switch ( request.what ) { - case 'getUserRules': - response = { - temporaryRules: ηm.tMatrix.toString(), - permanentRules: ηm.pMatrix.toString() - }; - break; + switch ( request.what ) { + case 'getUserRules': + response = { + temporaryRules: ηm.tMatrix.toString(), + permanentRules: ηm.pMatrix.toString() + }; + break; - case 'setUserRules': - if ( typeof request.temporaryRules === 'string' ) { - ηm.tMatrix.fromString(request.temporaryRules); - } - if ( typeof request.permanentRules === 'string' ) { - ηm.pMatrix.fromString(request.permanentRules); - ηm.saveMatrix(); - } - response = { - temporaryRules: ηm.tMatrix.toString(), - permanentRules: ηm.pMatrix.toString() - }; - break; + case 'setUserRules': + if ( typeof request.temporaryRules === 'string' ) { + ηm.tMatrix.fromString(request.temporaryRules); + } + if ( typeof request.permanentRules === 'string' ) { + ηm.pMatrix.fromString(request.permanentRules); + ηm.saveMatrix(); + } + response = { + temporaryRules: ηm.tMatrix.toString(), + permanentRules: ηm.pMatrix.toString() + }; + break; - default: - return vAPI.messaging.UNHANDLED; - } + default: + return vAPI.messaging.UNHANDLED; + } - callback(response); -}; + callback(response); + }; -vAPI.messaging.listen('user-rules.js', onMessage); + vAPI.messaging.listen('user-rules.js', onMessage); })(); @@ -711,87 +711,87 @@ vAPI.messaging.listen('user-rules.js', onMessage); (function() { -var ηm = ηMatrix; + var ηm = ηMatrix; -/******************************************************************************/ + /******************************************************************************/ -var prepEntries = function(entries) { - var ηmuri = ηm.URI; - var entry; - for ( var k in entries ) { - if ( entries.hasOwnProperty(k) === false ) { - continue; - } - entry = entries[k]; - if ( typeof entry.homeURL === 'string' ) { - entry.homeHostname = ηmuri.hostnameFromURI(entry.homeURL); - entry.homeDomain = ηmuri.domainFromHostname(entry.homeHostname); + var prepEntries = function(entries) { + var ηmuri = ηm.URI; + var entry; + for ( var k in entries ) { + if ( entries.hasOwnProperty(k) === false ) { + continue; + } + entry = entries[k]; + if ( typeof entry.homeURL === 'string' ) { + entry.homeHostname = ηmuri.hostnameFromURI(entry.homeURL); + entry.homeDomain = ηmuri.domainFromHostname(entry.homeHostname); + } } - } -}; + }; -/******************************************************************************/ + /******************************************************************************/ -var getLists = function(callback) { - var r = { - autoUpdate: ηm.userSettings.autoUpdate, - available: null, - cache: null, - current: ηm.liveHostsFiles, - blockedHostnameCount: ηm.ubiquitousBlacklist.count - }; - var onMetadataReady = function(entries) { - r.cache = entries; - prepEntries(r.cache); - callback(r); - }; - var onAvailableHostsFilesReady = function(lists) { - r.available = lists; - prepEntries(r.available); - ηm.assets.metadata(onMetadataReady); + var getLists = function(callback) { + var r = { + autoUpdate: ηm.userSettings.autoUpdate, + available: null, + cache: null, + current: ηm.liveHostsFiles, + blockedHostnameCount: ηm.ubiquitousBlacklist.count + }; + var onMetadataReady = function(entries) { + r.cache = entries; + prepEntries(r.cache); + callback(r); + }; + var onAvailableHostsFilesReady = function(lists) { + r.available = lists; + prepEntries(r.available); + ηm.assets.metadata(onMetadataReady); + }; + ηm.getAvailableHostsFiles(onAvailableHostsFilesReady); }; - ηm.getAvailableHostsFiles(onAvailableHostsFilesReady); -}; -/******************************************************************************/ + /******************************************************************************/ -var onMessage = function(request, sender, callback) { - var ηm = ηMatrix; + var onMessage = function(request, sender, callback) { + var ηm = ηMatrix; - // Async - switch ( request.what ) { - case 'getLists': - return getLists(callback); + // Async + switch ( request.what ) { + case 'getLists': + return getLists(callback); - default: - break; - } + default: + break; + } - // Sync - var response; + // Sync + var response; - switch ( request.what ) { - case 'purgeCache': - ηm.assets.purge(request.assetKey); - ηm.assets.remove('compiled/' + request.assetKey); - break; + switch ( request.what ) { + case 'purgeCache': + ηm.assets.purge(request.assetKey); + ηm.assets.remove('compiled/' + request.assetKey); + break; - case 'purgeAllCaches': - if ( request.hard ) { - ηm.assets.remove(/./); - } else { - ηm.assets.purge(/./, 'public_suffix_list.dat'); - } - break; + case 'purgeAllCaches': + if ( request.hard ) { + ηm.assets.remove(/./); + } else { + ηm.assets.purge(/./, 'public_suffix_list.dat'); + } + break; - default: - return vAPI.messaging.UNHANDLED; - } + default: + return vAPI.messaging.UNHANDLED; + } - callback(response); -}; + callback(response); + }; -vAPI.messaging.listen('hosts-files.js', onMessage); + vAPI.messaging.listen('hosts-files.js', onMessage); })(); @@ -802,162 +802,162 @@ vAPI.messaging.listen('hosts-files.js', onMessage); (function() { -var ηm = ηMatrix; - -/******************************************************************************/ - -var restoreUserData = function(userData) { - var countdown = 4; - var onCountdown = function() { - countdown -= 1; - if ( countdown === 0 ) { - vAPI.app.restart(); - } - }; + var ηm = ηMatrix; - var onAllRemoved = function() { - vAPI.storage.set(userData.settings, onCountdown); - vAPI.storage.set({ userMatrix: userData.rules }, onCountdown); - vAPI.storage.set({ liveHostsFiles: userData.hostsFiles }, onCountdown); - if ( userData.rawSettings instanceof Object ) { - ηMatrix.saveRawSettings(userData.rawSettings, onCountdown); - } - }; + /******************************************************************************/ - // If we are going to restore all, might as well wipe out clean local - // storage - ηm.XAL.keyvalRemoveAll(onAllRemoved); -}; + var restoreUserData = function(userData) { + var countdown = 4; + var onCountdown = function() { + countdown -= 1; + if ( countdown === 0 ) { + vAPI.app.restart(); + } + }; -/******************************************************************************/ + var onAllRemoved = function() { + vAPI.storage.set(userData.settings, onCountdown); + vAPI.storage.set({ userMatrix: userData.rules }, onCountdown); + vAPI.storage.set({ liveHostsFiles: userData.hostsFiles }, onCountdown); + if ( userData.rawSettings instanceof Object ) { + ηMatrix.saveRawSettings(userData.rawSettings, onCountdown); + } + }; -var resetUserData = function() { - var onAllRemoved = function() { - vAPI.app.restart(); + // If we are going to restore all, might as well wipe out clean local + // storage + ηm.XAL.keyvalRemoveAll(onAllRemoved); }; - ηm.XAL.keyvalRemoveAll(onAllRemoved); -}; - -/******************************************************************************/ -var onMessage = function(request, sender, callback) { - // Async - switch ( request.what ) { - default: - break; - } + /******************************************************************************/ - // Sync - var response; - - switch ( request.what ) { - case 'getAllUserData': - response = { - app: vAPI.app.name, - version: vAPI.app.version, - when: Date.now(), - settings: ηm.userSettings, - rules: ηm.pMatrix.toString(), - hostsFiles: ηm.liveHostsFiles, - rawSettings: ηm.rawSettings + var resetUserData = function() { + var onAllRemoved = function() { + vAPI.app.restart(); }; - break; + ηm.XAL.keyvalRemoveAll(onAllRemoved); + }; - case 'getSomeStats': - response = { - version: vAPI.app.version, - storageUsed: ηm.storageUsed - }; - break; + /******************************************************************************/ - case 'restoreAllUserData': - restoreUserData(request.userData); - break; + var onMessage = function(request, sender, callback) { + // Async + switch ( request.what ) { + default: + break; + } - case 'resetAllUserData': - resetUserData(); - break; + // Sync + var response; + + switch ( request.what ) { + case 'getAllUserData': + response = { + app: vAPI.app.name, + version: vAPI.app.version, + when: Date.now(), + settings: ηm.userSettings, + rules: ηm.pMatrix.toString(), + hostsFiles: ηm.liveHostsFiles, + rawSettings: ηm.rawSettings + }; + break; - default: - return vAPI.messaging.UNHANDLED; - } + case 'getSomeStats': + response = { + version: vAPI.app.version, + storageUsed: ηm.storageUsed + }; + break; - callback(response); -}; + case 'restoreAllUserData': + restoreUserData(request.userData); + break; -vAPI.messaging.listen('about.js', onMessage); + case 'resetAllUserData': + resetUserData(); + break; -/******************************************************************************/ -/******************************************************************************/ + default: + return vAPI.messaging.UNHANDLED; + } -// logger-ui.js + callback(response); + }; -(function() { + vAPI.messaging.listen('about.js', onMessage); -/******************************************************************************/ + /******************************************************************************/ + /******************************************************************************/ -var ηm = ηMatrix, - loggerURL = vAPI.getURL('logger-ui.html'); + // logger-ui.js -/******************************************************************************/ + (function() { -var onMessage = function(request, sender, callback) { - // Async - switch ( request.what ) { - default: - break; - } + /******************************************************************************/ - // Sync - var response; + var ηm = ηMatrix, + loggerURL = vAPI.getURL('logger-ui.html'); - switch ( request.what ) { - case 'readMany': - if ( - ηm.logger.ownerId !== undefined && - request.ownerId !== ηm.logger.ownerId - ) { - response = { unavailable: true }; - break; - } - var tabIds = {}; - for ( var tabId in ηm.pageStores ) { - var pageStore = ηm.pageStoreFromTabId(tabId); - if ( pageStore === null ) { continue; } - if ( pageStore.rawUrl.startsWith(loggerURL) ) { continue; } - tabIds[tabId] = pageStore.title || pageStore.rawUrl; - } - response = { - colorBlind: false, - entries: ηm.logger.readAll(request.ownerId), - maxLoggedRequests: ηm.userSettings.maxLoggedRequests, - noTabId: vAPI.noTabId, - tabIds: tabIds, - tabIdsToken: ηm.pageStoresToken - }; - break; + /******************************************************************************/ - case 'releaseView': - if ( request.ownerId === ηm.logger.ownerId ) { - ηm.logger.ownerId = undefined; - } - break; + var onMessage = function(request, sender, callback) { + // Async + switch ( request.what ) { + default: + break; + } - default: - return vAPI.messaging.UNHANDLED; - } + // Sync + var response; + + switch ( request.what ) { + case 'readMany': + if ( + ηm.logger.ownerId !== undefined && + request.ownerId !== ηm.logger.ownerId + ) { + response = { unavailable: true }; + break; + } + var tabIds = {}; + for ( var tabId in ηm.pageStores ) { + var pageStore = ηm.pageStoreFromTabId(tabId); + if ( pageStore === null ) { continue; } + if ( pageStore.rawUrl.startsWith(loggerURL) ) { continue; } + tabIds[tabId] = pageStore.title || pageStore.rawUrl; + } + response = { + colorBlind: false, + entries: ηm.logger.readAll(request.ownerId), + maxLoggedRequests: ηm.userSettings.maxLoggedRequests, + noTabId: vAPI.noTabId, + tabIds: tabIds, + tabIdsToken: ηm.pageStoresToken + }; + break; + + case 'releaseView': + if ( request.ownerId === ηm.logger.ownerId ) { + ηm.logger.ownerId = undefined; + } + break; + + default: + return vAPI.messaging.UNHANDLED; + } - callback(response); -}; + callback(response); + }; -vAPI.messaging.listen('logger-ui.js', onMessage); + vAPI.messaging.listen('logger-ui.js', onMessage); -/******************************************************************************/ + /******************************************************************************/ -})(); + })(); -/******************************************************************************/ -/******************************************************************************/ + /******************************************************************************/ + /******************************************************************************/ })(); diff --git a/js/pagestats.js b/js/pagestats.js index 646bf01..276f099 100644 --- a/js/pagestats.js +++ b/js/pagestats.js @@ -27,248 +27,248 @@ ηMatrix.pageStoreFactory = (function() { -/******************************************************************************/ + /******************************************************************************/ -var ηm = ηMatrix; + var ηm = ηMatrix; -/******************************************************************************/ + /******************************************************************************/ -var BlockedCollapsibles = function() { - this.boundPruneAsyncCallback = this.pruneAsyncCallback.bind(this); - this.blocked = new Map(); - this.hash = 0; - this.timer = null; -}; - -BlockedCollapsibles.prototype = { - - shelfLife: 10 * 1000, - - add: function(type, url, isSpecific) { - if ( this.blocked.size === 0 ) { this.pruneAsync(); } - var now = Date.now() / 1000 | 0; - // The following "trick" is to encode the specifity into the lsb of the - // time stamp so as to avoid to have to allocate a memory structure to - // store both time stamp and specificity. - if ( isSpecific ) { - now |= 0x00000001; - } else { - now &= 0xFFFFFFFE; - } - this.blocked.set(type + ' ' + url, now); - this.hash = now; - }, - - reset: function() { - this.blocked.clear(); + var BlockedCollapsibles = function() { + this.boundPruneAsyncCallback = this.pruneAsyncCallback.bind(this); + this.blocked = new Map(); this.hash = 0; - if ( this.timer !== null ) { - clearTimeout(this.timer); - this.timer = null; - } - }, - - pruneAsync: function() { - if ( this.timer === null ) { - this.timer = vAPI.setTimeout( - this.boundPruneAsyncCallback, - this.shelfLife * 2 - ); - } - }, - - pruneAsyncCallback: function() { this.timer = null; - var obsolete = Date.now() - this.shelfLife; - for ( var entry of this.blocked ) { - if ( entry[1] <= obsolete ) { - this.blocked.delete(entry[0]); + }; + + BlockedCollapsibles.prototype = { + + shelfLife: 10 * 1000, + + add: function(type, url, isSpecific) { + if ( this.blocked.size === 0 ) { this.pruneAsync(); } + var now = Date.now() / 1000 | 0; + // The following "trick" is to encode the specifity into the lsb of the + // time stamp so as to avoid to have to allocate a memory structure to + // store both time stamp and specificity. + if ( isSpecific ) { + now |= 0x00000001; + } else { + now &= 0xFFFFFFFE; } - } - if ( this.blocked.size !== 0 ) { this.pruneAsync(); } - } -}; - -/******************************************************************************/ + this.blocked.set(type + ' ' + url, now); + this.hash = now; + }, + + reset: function() { + this.blocked.clear(); + this.hash = 0; + if ( this.timer !== null ) { + clearTimeout(this.timer); + this.timer = null; + } + }, + + pruneAsync: function() { + if ( this.timer === null ) { + this.timer = vAPI.setTimeout( + this.boundPruneAsyncCallback, + this.shelfLife * 2 + ); + } + }, -// Ref: Given a URL, returns a (somewhat) unique 32-bit value -// Based on: FNV32a -// http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-reference-source -// The rest is custom, suited for uMatrix. - -var PageStore = function(tabContext) { - this.hostnameTypeCells = new Map(); - this.domains = new Set(); - this.blockedCollapsibles = new BlockedCollapsibles(); - this.requestStats = ηm.requestStatsFactory(); - this.off = false; - this.init(tabContext); -}; - -PageStore.prototype = { - - collapsibleTypes: new Set([ 'image' ]), - pageStoreJunkyard: [], - - init: function(tabContext) { - this.tabId = tabContext.tabId; - this.rawUrl = tabContext.rawURL; - this.pageUrl = tabContext.normalURL; - this.pageHostname = tabContext.rootHostname; - this.pageDomain = tabContext.rootDomain; - this.title = ''; - this.hostnameTypeCells.clear(); - this.domains.clear(); - this.allHostnamesString = ' '; - this.blockedCollapsibles.reset(); - this.requestStats.reset(); - this.distinctRequestCount = 0; - this.perLoadAllowedRequestCount = 0; - this.perLoadBlockedRequestCount = 0; - this.has3pReferrer = false; - this.hasMixedContent = false; - this.hasNoscriptTags = false; - this.hasWebWorkers = false; - this.incinerationTimer = null; - this.mtxContentModifiedTime = 0; - this.mtxCountModifiedTime = 0; - return this; - }, - - dispose: function() { - this.rawUrl = ''; - this.pageUrl = ''; - this.pageHostname = ''; - this.pageDomain = ''; - this.title = ''; - this.hostnameTypeCells.clear(); - this.domains.clear(); - this.allHostnamesString = ' '; - this.blockedCollapsibles.reset(); - if ( this.incinerationTimer !== null ) { - clearTimeout(this.incinerationTimer); - this.incinerationTimer = null; - } - if ( this.pageStoreJunkyard.length < 8 ) { - this.pageStoreJunkyard.push(this); - } - }, - - cacheBlockedCollapsible: function(type, url, specificity) { - if ( this.collapsibleTypes.has(type) ) { - this.blockedCollapsibles.add( - type, - url, - specificity !== 0 && specificity < 5 - ); + pruneAsyncCallback: function() { + this.timer = null; + var obsolete = Date.now() - this.shelfLife; + for ( var entry of this.blocked ) { + if ( entry[1] <= obsolete ) { + this.blocked.delete(entry[0]); + } + } + if ( this.blocked.size !== 0 ) { this.pruneAsync(); } } - }, - - lookupBlockedCollapsibles: function(request, response) { - var tabContext = ηm.tabContextManager.lookup(this.tabId); - if ( tabContext === null ) { return; } - - var collapseBlacklisted = ηm.userSettings.collapseBlacklisted, - collapseBlocked = ηm.userSettings.collapseBlocked, - entry; - - var blockedResources = response.blockedResources; - - if ( - Array.isArray(request.toFilter) && - request.toFilter.length !== 0 - ) { - var roothn = tabContext.rootHostname, - hnFromURI = ηm.URI.hostnameFromURI, - tMatrix = ηm.tMatrix; - for ( entry of request.toFilter ) { - if ( tMatrix.mustBlock(roothn, hnFromURI(entry.url), entry.type) === false ) { - continue; + }; + + /******************************************************************************/ + + // Ref: Given a URL, returns a (somewhat) unique 32-bit value + // Based on: FNV32a + // http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-reference-source + // The rest is custom, suited for uMatrix. + + var PageStore = function(tabContext) { + this.hostnameTypeCells = new Map(); + this.domains = new Set(); + this.blockedCollapsibles = new BlockedCollapsibles(); + this.requestStats = ηm.requestStatsFactory(); + this.off = false; + this.init(tabContext); + }; + + PageStore.prototype = { + + collapsibleTypes: new Set([ 'image' ]), + pageStoreJunkyard: [], + + init: function(tabContext) { + this.tabId = tabContext.tabId; + this.rawUrl = tabContext.rawURL; + this.pageUrl = tabContext.normalURL; + this.pageHostname = tabContext.rootHostname; + this.pageDomain = tabContext.rootDomain; + this.title = ''; + this.hostnameTypeCells.clear(); + this.domains.clear(); + this.allHostnamesString = ' '; + this.blockedCollapsibles.reset(); + this.requestStats.reset(); + this.distinctRequestCount = 0; + this.perLoadAllowedRequestCount = 0; + this.perLoadBlockedRequestCount = 0; + this.has3pReferrer = false; + this.hasMixedContent = false; + this.hasNoscriptTags = false; + this.hasWebWorkers = false; + this.incinerationTimer = null; + this.mtxContentModifiedTime = 0; + this.mtxCountModifiedTime = 0; + return this; + }, + + dispose: function() { + this.rawUrl = ''; + this.pageUrl = ''; + this.pageHostname = ''; + this.pageDomain = ''; + this.title = ''; + this.hostnameTypeCells.clear(); + this.domains.clear(); + this.allHostnamesString = ' '; + this.blockedCollapsibles.reset(); + if ( this.incinerationTimer !== null ) { + clearTimeout(this.incinerationTimer); + this.incinerationTimer = null; + } + if ( this.pageStoreJunkyard.length < 8 ) { + this.pageStoreJunkyard.push(this); + } + }, + + cacheBlockedCollapsible: function(type, url, specificity) { + if ( this.collapsibleTypes.has(type) ) { + this.blockedCollapsibles.add( + type, + url, + specificity !== 0 && specificity < 5 + ); + } + }, + + lookupBlockedCollapsibles: function(request, response) { + var tabContext = ηm.tabContextManager.lookup(this.tabId); + if ( tabContext === null ) { return; } + + var collapseBlacklisted = ηm.userSettings.collapseBlacklisted, + collapseBlocked = ηm.userSettings.collapseBlocked, + entry; + + var blockedResources = response.blockedResources; + + if ( + Array.isArray(request.toFilter) && + request.toFilter.length !== 0 + ) { + var roothn = tabContext.rootHostname, + hnFromURI = ηm.URI.hostnameFromURI, + tMatrix = ηm.tMatrix; + for ( entry of request.toFilter ) { + if ( tMatrix.mustBlock(roothn, hnFromURI(entry.url), entry.type) === false ) { + continue; + } + blockedResources.push([ + entry.type + ' ' + entry.url, + collapseBlocked || + collapseBlacklisted && tMatrix.specificityRegister !== 0 && + tMatrix.specificityRegister < 5 + ]); } + } + + if ( this.blockedCollapsibles.hash === response.hash ) { return; } + response.hash = this.blockedCollapsibles.hash; + + for ( entry of this.blockedCollapsibles.blocked ) { blockedResources.push([ - entry.type + ' ' + entry.url, - collapseBlocked || - collapseBlacklisted && tMatrix.specificityRegister !== 0 && - tMatrix.specificityRegister < 5 + entry[0], + collapseBlocked || collapseBlacklisted && (entry[1] & 1) !== 0 ]); } - } - - if ( this.blockedCollapsibles.hash === response.hash ) { return; } - response.hash = this.blockedCollapsibles.hash; + }, - for ( entry of this.blockedCollapsibles.blocked ) { - blockedResources.push([ - entry[0], - collapseBlocked || collapseBlacklisted && (entry[1] & 1) !== 0 - ]); - } - }, + recordRequest: function(type, url, block) { + if ( block !== false ) { + this.perLoadBlockedRequestCount++; + } else { + this.perLoadAllowedRequestCount++; + } - recordRequest: function(type, url, block) { - if ( block !== false ) { - this.perLoadBlockedRequestCount++; - } else { - this.perLoadAllowedRequestCount++; - } - - // Store distinct network requests. This is used to: - // - remember which hostname/type were seen - // - count the number of distinct URLs for any given - // hostname-type pair - var hostname = ηm.URI.hostnameFromURI(url), - key = hostname + ' ' + type, - uids = this.hostnameTypeCells.get(key); - if ( uids === undefined ) { - this.hostnameTypeCells.set(key, (uids = new Set())); - } else if ( uids.size > 99 ) { - return; - } - var uid = this.uidFromURL(url); - if ( uids.has(uid) ) { return; } - uids.add(uid); + // Store distinct network requests. This is used to: + // - remember which hostname/type were seen + // - count the number of distinct URLs for any given + // hostname-type pair + var hostname = ηm.URI.hostnameFromURI(url), + key = hostname + ' ' + type, + uids = this.hostnameTypeCells.get(key); + if ( uids === undefined ) { + this.hostnameTypeCells.set(key, (uids = new Set())); + } else if ( uids.size > 99 ) { + return; + } + var uid = this.uidFromURL(url); + if ( uids.has(uid) ) { return; } + uids.add(uid); - // Count blocked/allowed requests - this.requestStats.record(type, block); + // Count blocked/allowed requests + this.requestStats.record(type, block); - // https://github.com/gorhill/httpswitchboard/issues/306 - // If it is recorded locally, record globally - ηm.requestStats.record(type, block); - ηm.updateBadgeAsync(this.tabId); + // https://github.com/gorhill/httpswitchboard/issues/306 + // If it is recorded locally, record globally + ηm.requestStats.record(type, block); + ηm.updateBadgeAsync(this.tabId); - // this.distinctRequestCount++; - this.mtxCountModifiedTime = Date.now(); + // this.distinctRequestCount++; + this.mtxCountModifiedTime = Date.now(); - if ( this.domains.has(hostname) === false ) { - this.domains.add(hostname); - this.allHostnamesString += hostname + ' '; - this.mtxContentModifiedTime = Date.now(); - } - }, - - uidFromURL: function(uri) { - var hint = 0x811c9dc5, - i = uri.length; - while ( i-- ) { - hint ^= uri.charCodeAt(i) | 0; - hint += (hint<<1) + (hint<<4) + (hint<<7) + (hint<<8) + (hint<<24) | 0; - hint >>>= 0; + if ( this.domains.has(hostname) === false ) { + this.domains.add(hostname); + this.allHostnamesString += hostname + ' '; + this.mtxContentModifiedTime = Date.now(); + } + }, + + uidFromURL: function(uri) { + var hint = 0x811c9dc5, + i = uri.length; + while ( i-- ) { + hint ^= uri.charCodeAt(i) | 0; + hint += (hint<<1) + (hint<<4) + (hint<<7) + (hint<<8) + (hint<<24) | 0; + hint >>>= 0; + } + return hint; } - return hint; - } -}; + }; -/******************************************************************************/ + /******************************************************************************/ -return function pageStoreFactory(tabContext) { - var entry = PageStore.prototype.pageStoreJunkyard.pop(); - if ( entry ) { - return entry.init(tabContext); - } - return new PageStore(tabContext); -}; + return function pageStoreFactory(tabContext) { + var entry = PageStore.prototype.pageStoreJunkyard.pop(); + if ( entry ) { + return entry.init(tabContext); + } + return new PageStore(tabContext); + }; -/******************************************************************************/ + /******************************************************************************/ })(); diff --git a/js/popup.js b/js/popup.js index 5ece83a..2197c5c 100644 --- a/js/popup.js +++ b/js/popup.js @@ -31,1509 +31,1509 @@ (function() { -/******************************************************************************/ -/******************************************************************************/ - -// Stuff which is good to do very early so as to avoid visual glitches. + /******************************************************************************/ + /******************************************************************************/ -(function() { - var paneContentPaddingTop = vAPI.localStorage.getItem('paneContentPaddingTop'), - touchDevice = vAPI.localStorage.getItem('touchDevice'); + // Stuff which is good to do very early so as to avoid visual glitches. - if ( typeof paneContentPaddingTop === 'string' ) { - document.querySelector('.paneContent').style.setProperty( - 'padding-top', - paneContentPaddingTop - ); - } - if ( touchDevice === 'true' ) { - document.body.setAttribute('data-touch', 'true'); - } else { - document.addEventListener('touchstart', function onTouched(ev) { - document.removeEventListener(ev.type, onTouched); + (function() { + var paneContentPaddingTop = vAPI.localStorage.getItem('paneContentPaddingTop'), + touchDevice = vAPI.localStorage.getItem('touchDevice'); + + if ( typeof paneContentPaddingTop === 'string' ) { + document.querySelector('.paneContent').style.setProperty( + 'padding-top', + paneContentPaddingTop + ); + } + if ( touchDevice === 'true' ) { document.body.setAttribute('data-touch', 'true'); - vAPI.localStorage.setItem('touchDevice', 'true'); - resizePopup(); - }); - } -})(); - -var popupWasResized = function() { - document.body.setAttribute('data-resize-popup', ''); -}; - -var resizePopup = (function() { - var timer; - var fix = function() { - timer = undefined; - var doc = document; - // Manually adjust the position of the main matrix according to the - // height of the toolbar/matrix header. - var paddingTop = (doc.querySelector('.paneHead').clientHeight + 2) + 'px', - paneContent = doc.querySelector('.paneContent'); - if ( paddingTop !== paneContent.style.paddingTop ) { - paneContent.style.setProperty('padding-top', paddingTop); - vAPI.localStorage.setItem('paneContentPaddingTop', paddingTop); + } else { + document.addEventListener('touchstart', function onTouched(ev) { + document.removeEventListener(ev.type, onTouched); + document.body.setAttribute('data-touch', 'true'); + vAPI.localStorage.setItem('touchDevice', 'true'); + resizePopup(); + }); } - document.body.classList.toggle( - 'hConstrained', - window.innerWidth < document.body.clientWidth - ); - popupWasResized(); + })(); + + var popupWasResized = function() { + document.body.setAttribute('data-resize-popup', ''); }; - return function() { - if ( timer !== undefined ) { - clearTimeout(timer); - } - timer = vAPI.setTimeout(fix, 97); + + var resizePopup = (function() { + var timer; + var fix = function() { + timer = undefined; + var doc = document; + // Manually adjust the position of the main matrix according to the + // height of the toolbar/matrix header. + var paddingTop = (doc.querySelector('.paneHead').clientHeight + 2) + 'px', + paneContent = doc.querySelector('.paneContent'); + if ( paddingTop !== paneContent.style.paddingTop ) { + paneContent.style.setProperty('padding-top', paddingTop); + vAPI.localStorage.setItem('paneContentPaddingTop', paddingTop); + } + document.body.classList.toggle( + 'hConstrained', + window.innerWidth < document.body.clientWidth + ); + popupWasResized(); + }; + return function() { + if ( timer !== undefined ) { + clearTimeout(timer); + } + timer = vAPI.setTimeout(fix, 97); + }; + })(); + + /******************************************************************************/ + /******************************************************************************/ + + // Must be consistent with definitions in matrix.js + var Dark = 0x80; + var Red = 1; + var Green = 2; + var DarkRed = Dark | Red; + var DarkGreen = Dark | Green; + + var matrixSnapshot = {}; + var groupsSnapshot = []; + var allHostnamesSnapshot = 'do not leave this initial string empty'; + + var matrixCellHotspots = null; + + var matrixHeaderPrettyNames = { + 'all': '', + 'cookie': '', + 'css': '', + 'image': '', + 'media': '', + 'script': '', + 'xhr': '', + 'frame': '', + 'other': '' }; -})(); -/******************************************************************************/ -/******************************************************************************/ + var firstPartyLabel = ''; + var blacklistedHostnamesLabel = ''; -// Must be consistent with definitions in matrix.js -var Dark = 0x80; -var Red = 1; -var Green = 2; -var DarkRed = Dark | Red; -var DarkGreen = Dark | Green; - -var matrixSnapshot = {}; -var groupsSnapshot = []; -var allHostnamesSnapshot = 'do not leave this initial string empty'; - -var matrixCellHotspots = null; - -var matrixHeaderPrettyNames = { - 'all': '', - 'cookie': '', - 'css': '', - 'image': '', - 'media': '', - 'script': '', - 'xhr': '', - 'frame': '', - 'other': '' -}; - -var firstPartyLabel = ''; -var blacklistedHostnamesLabel = ''; - -var expandosIdGenerator = 1; -var nodeToExpandosMap = (function() { - if ( typeof window.Map === 'function' ) { - return new window.Map(); - } -})(); + var expandosIdGenerator = 1; + var nodeToExpandosMap = (function() { + if ( typeof window.Map === 'function' ) { + return new window.Map(); + } + })(); -var expandosFromNode = function(node) { - if ( - node instanceof HTMLElement === false && - typeof node.nodeAt === 'function' - ) { - node = node.nodeAt(0); - } - if ( nodeToExpandosMap ) { - var expandosId = node.getAttribute('data-expandos'); - if ( !expandosId ) { - expandosId = '' + (expandosIdGenerator++); - node.setAttribute('data-expandos', expandosId); + var expandosFromNode = function(node) { + if ( + node instanceof HTMLElement === false && + typeof node.nodeAt === 'function' + ) { + node = node.nodeAt(0); } - var expandos = nodeToExpandosMap.get(expandosId); - if ( expandos === undefined ) { - nodeToExpandosMap.set(expandosId, (expandos = Object.create(null))); + if ( nodeToExpandosMap ) { + var expandosId = node.getAttribute('data-expandos'); + if ( !expandosId ) { + expandosId = '' + (expandosIdGenerator++); + node.setAttribute('data-expandos', expandosId); + } + var expandos = nodeToExpandosMap.get(expandosId); + if ( expandos === undefined ) { + nodeToExpandosMap.set(expandosId, (expandos = Object.create(null))); + } + return expandos; } - return expandos; - } - return node; -}; + return node; + }; -/******************************************************************************/ -/******************************************************************************/ + /******************************************************************************/ + /******************************************************************************/ -function getUserSetting(setting) { + function getUserSetting(setting) { return matrixSnapshot.userSettings[setting]; } -function setUserSetting(setting, value) { - matrixSnapshot.userSettings[setting] = value; - vAPI.messaging.send('popup.js', { - what: 'userSettings', - name: setting, - value: value - }); -} + function setUserSetting(setting, value) { + matrixSnapshot.userSettings[setting] = value; + vAPI.messaging.send('popup.js', { + what: 'userSettings', + name: setting, + value: value + }); + } -/******************************************************************************/ + /******************************************************************************/ -function getUISetting(setting) { - var r = vAPI.localStorage.getItem(setting); - if ( typeof r !== 'string' ) { - return undefined; + function getUISetting(setting) { + var r = vAPI.localStorage.getItem(setting); + if ( typeof r !== 'string' ) { + return undefined; + } + return JSON.parse(r); } - return JSON.parse(r); -} -function setUISetting(setting, value) { - vAPI.localStorage.setItem( - setting, - JSON.stringify(value) - ); -} + function setUISetting(setting, value) { + vAPI.localStorage.setItem( + setting, + JSON.stringify(value) + ); + } -/******************************************************************************/ + /******************************************************************************/ -function updateMatrixSnapshot() { - matrixSnapshotPoller.pollNow(); -} + function updateMatrixSnapshot() { + matrixSnapshotPoller.pollNow(); + } -/******************************************************************************/ + /******************************************************************************/ + + // For display purpose, create four distinct groups of rows: + // 0th: literal "1st-party" row + // 1st: page domain's related + // 2nd: whitelisted + // 3rd: graylisted + // 4th: blacklisted -// For display purpose, create four distinct groups of rows: -// 0th: literal "1st-party" row -// 1st: page domain's related -// 2nd: whitelisted -// 3rd: graylisted -// 4th: blacklisted - -function getGroupStats() { - - // Try to not reshuffle groups around while popup is opened if - // no new hostname added. - var latestDomainListSnapshot = Object.keys(matrixSnapshot.rows).sort().join(); - if ( latestDomainListSnapshot === allHostnamesSnapshot ) { - return groupsSnapshot; - } - allHostnamesSnapshot = latestDomainListSnapshot; - - // First, group according to whether at least one node in the domain - // hierarchy is white or blacklisted - var pageDomain = matrixSnapshot.domain; - var rows = matrixSnapshot.rows; - var anyTypeOffset = matrixSnapshot.headerIndices.get('*'); - var hostname, domain; - var row, color, count, groupIndex; - var domainToGroupMap = {}; - - // These have hard-coded position which cannot be overriden - domainToGroupMap['1st-party'] = 0; - domainToGroupMap[pageDomain] = 1; - - // 1st pass: domain wins if it has an explicit rule or a count - for ( hostname in rows ) { - if ( rows.hasOwnProperty(hostname) === false ) { - continue; + function getGroupStats() { + + // Try to not reshuffle groups around while popup is opened if + // no new hostname added. + var latestDomainListSnapshot = Object.keys(matrixSnapshot.rows).sort().join(); + if ( latestDomainListSnapshot === allHostnamesSnapshot ) { + return groupsSnapshot; } - if ( hostname === '*' || hostname === '1st-party' ) { - continue; + allHostnamesSnapshot = latestDomainListSnapshot; + + // First, group according to whether at least one node in the domain + // hierarchy is white or blacklisted + var pageDomain = matrixSnapshot.domain; + var rows = matrixSnapshot.rows; + var anyTypeOffset = matrixSnapshot.headerIndices.get('*'); + var hostname, domain; + var row, color, count, groupIndex; + var domainToGroupMap = {}; + + // These have hard-coded position which cannot be overriden + domainToGroupMap['1st-party'] = 0; + domainToGroupMap[pageDomain] = 1; + + // 1st pass: domain wins if it has an explicit rule or a count + for ( hostname in rows ) { + if ( rows.hasOwnProperty(hostname) === false ) { + continue; + } + if ( hostname === '*' || hostname === '1st-party' ) { + continue; + } + domain = rows[hostname].domain; + if ( domain === pageDomain || hostname !== domain ) { + continue; + } + row = rows[domain]; + color = row.temporary[anyTypeOffset]; + if ( color === DarkGreen ) { + domainToGroupMap[domain] = 2; + continue; + } + if ( color === DarkRed ) { + domainToGroupMap[domain] = 4; + continue; + } + count = row.counts[anyTypeOffset]; + if ( count !== 0 ) { + domainToGroupMap[domain] = 3; + continue; + } } - domain = rows[hostname].domain; - if ( domain === pageDomain || hostname !== domain ) { - continue; + // 2nd pass: green wins + for ( hostname in rows ) { + if ( rows.hasOwnProperty(hostname) === false ) { + continue; + } + row = rows[hostname]; + domain = row.domain; + if ( domainToGroupMap.hasOwnProperty(domain) ) { + continue; + } + color = row.temporary[anyTypeOffset]; + if ( color === DarkGreen ) { + domainToGroupMap[domain] = 2; + } } - row = rows[domain]; - color = row.temporary[anyTypeOffset]; - if ( color === DarkGreen ) { - domainToGroupMap[domain] = 2; - continue; + // 3rd pass: gray with count wins + for ( hostname in rows ) { + if ( rows.hasOwnProperty(hostname) === false ) { + continue; + } + row = rows[hostname]; + domain = row.domain; + if ( domainToGroupMap.hasOwnProperty(domain) ) { + continue; + } + color = row.temporary[anyTypeOffset]; + count = row.counts[anyTypeOffset]; + if ( color !== DarkRed && count !== 0 ) { + domainToGroupMap[domain] = 3; + } } - if ( color === DarkRed ) { - domainToGroupMap[domain] = 4; - continue; + // 4th pass: red wins whatever is left + for ( hostname in rows ) { + if ( rows.hasOwnProperty(hostname) === false ) { + continue; + } + row = rows[hostname]; + domain = row.domain; + if ( domainToGroupMap.hasOwnProperty(domain) ) { + continue; + } + color = row.temporary[anyTypeOffset]; + if ( color === DarkRed ) { + domainToGroupMap[domain] = 4; + } } - count = row.counts[anyTypeOffset]; - if ( count !== 0 ) { + // 5th pass: gray wins whatever is left + for ( hostname in rows ) { + if ( rows.hasOwnProperty(hostname) === false ) { + continue; + } + domain = rows[hostname].domain; + if ( domainToGroupMap.hasOwnProperty(domain) ) { + continue; + } domainToGroupMap[domain] = 3; - continue; - } - } - // 2nd pass: green wins - for ( hostname in rows ) { - if ( rows.hasOwnProperty(hostname) === false ) { - continue; } - row = rows[hostname]; - domain = row.domain; - if ( domainToGroupMap.hasOwnProperty(domain) ) { - continue; - } - color = row.temporary[anyTypeOffset]; - if ( color === DarkGreen ) { - domainToGroupMap[domain] = 2; + + // Last pass: put each domain in a group + var groups = [ {}, {}, {}, {}, {} ]; + var group; + for ( hostname in rows ) { + if ( rows.hasOwnProperty(hostname) === false ) { + continue; + } + if ( hostname === '*' ) { + continue; + } + domain = rows[hostname].domain; + groupIndex = domainToGroupMap[domain]; + group = groups[groupIndex]; + if ( group.hasOwnProperty(domain) === false ) { + group[domain] = {}; + } + group[domain][hostname] = true; } + + groupsSnapshot = groups; + + return groups; } - // 3rd pass: gray with count wins - for ( hostname in rows ) { - if ( rows.hasOwnProperty(hostname) === false ) { - continue; - } - row = rows[hostname]; - domain = row.domain; - if ( domainToGroupMap.hasOwnProperty(domain) ) { - continue; - } - color = row.temporary[anyTypeOffset]; - count = row.counts[anyTypeOffset]; - if ( color !== DarkRed && count !== 0 ) { - domainToGroupMap[domain] = 3; - } + + /******************************************************************************/ + + // helpers + + function getTemporaryColor(hostname, type) { + return matrixSnapshot.rows[hostname].temporary[matrixSnapshot.headerIndices.get(type)]; } - // 4th pass: red wins whatever is left - for ( hostname in rows ) { - if ( rows.hasOwnProperty(hostname) === false ) { - continue; - } - row = rows[hostname]; - domain = row.domain; - if ( domainToGroupMap.hasOwnProperty(domain) ) { - continue; - } - color = row.temporary[anyTypeOffset]; - if ( color === DarkRed ) { - domainToGroupMap[domain] = 4; - } + + function getPermanentColor(hostname, type) { + return matrixSnapshot.rows[hostname].permanent[matrixSnapshot.headerIndices.get(type)]; + } + + function addCellClass(cell, hostname, type) { + var cl = cell.classList; + cl.add('matCell'); + cl.add('t' + getTemporaryColor(hostname, type).toString(16)); + cl.add('p' + getPermanentColor(hostname, type).toString(16)); } - // 5th pass: gray wins whatever is left - for ( hostname in rows ) { - if ( rows.hasOwnProperty(hostname) === false ) { - continue; + + /******************************************************************************/ + + // This is required for when we update the matrix while it is open: + // the user might have collapsed/expanded one or more domains, and we don't + // want to lose all his hardwork. + + function getCollapseState(domain) { + var states = getUISetting('popupCollapseSpecificDomains'); + if ( typeof states === 'object' && states[domain] !== undefined ) { + return states[domain]; } - domain = rows[hostname].domain; - if ( domainToGroupMap.hasOwnProperty(domain) ) { - continue; + return matrixSnapshot.collapseAllDomains === true; + } + + function toggleCollapseState(elem) { + if ( elem.ancestors('#matHead.collapsible').length > 0 ) { + toggleMainCollapseState(elem); + } else { + toggleSpecificCollapseState(elem); } - domainToGroupMap[domain] = 3; + popupWasResized(); } - // Last pass: put each domain in a group - var groups = [ {}, {}, {}, {}, {} ]; - var group; - for ( hostname in rows ) { - if ( rows.hasOwnProperty(hostname) === false ) { - continue; + function toggleMainCollapseState(uelem) { + var matHead = uelem.ancestors('#matHead.collapsible').toggleClass('collapsed'); + var collapsed = matrixSnapshot.collapseAllDomains = matHead.hasClass('collapsed'); + uDom('#matList .matSection.collapsible').toggleClass('collapsed', collapsed); + setUserSetting('popupCollapseAllDomains', collapsed); + + var specificCollapseStates = getUISetting('popupCollapseSpecificDomains') || {}; + var domains = Object.keys(specificCollapseStates); + var i = domains.length; + var domain; + while ( i-- ) { + domain = domains[i]; + if ( specificCollapseStates[domain] === collapsed ) { + delete specificCollapseStates[domain]; + } } - if ( hostname === '*' ) { - continue; + setUISetting('popupCollapseSpecificDomains', specificCollapseStates); + } + + function toggleSpecificCollapseState(uelem) { + // Remember collapse state forever, but only if it is different + // from main collapse switch. + var section = uelem.ancestors('.matSection.collapsible').toggleClass('collapsed'), + domain = expandosFromNode(section).domain, + collapsed = section.hasClass('collapsed'), + mainCollapseState = matrixSnapshot.collapseAllDomains === true, + specificCollapseStates = getUISetting('popupCollapseSpecificDomains') || {}; + if ( collapsed !== mainCollapseState ) { + specificCollapseStates[domain] = collapsed; + setUISetting('popupCollapseSpecificDomains', specificCollapseStates); + } else if ( specificCollapseStates[domain] !== undefined ) { + delete specificCollapseStates[domain]; + setUISetting('popupCollapseSpecificDomains', specificCollapseStates); } - domain = rows[hostname].domain; - groupIndex = domainToGroupMap[domain]; - group = groups[groupIndex]; - if ( group.hasOwnProperty(domain) === false ) { - group[domain] = {}; + } + + /******************************************************************************/ + + // Update count value of matrix cells(s) + + function updateMatrixCounts() { + var matCells = uDom('.matrix .matRow.rw > .matCell'), + i = matCells.length, + matRow, matCell, count, counts, + headerIndices = matrixSnapshot.headerIndices, + rows = matrixSnapshot.rows, + expandos; + while ( i-- ) { + matCell = matCells.nodeAt(i); + expandos = expandosFromNode(matCell); + if ( expandos.hostname === '*' || expandos.reqType === '*' ) { + continue; + } + matRow = matCell.parentNode; + counts = matRow.classList.contains('meta') ? 'totals' : 'counts'; + count = rows[expandos.hostname][counts][headerIndices.get(expandos.reqType)]; + if ( count === expandos.count ) { continue; } + expandos.count = count; + matCell.textContent = cellTextFromCount(count); } - group[domain][hostname] = true; } - groupsSnapshot = groups; + function cellTextFromCount(count) { + if ( count === 0 ) { return '\u00A0'; } + if ( count < 100 ) { return count; } + return '99+'; + } - return groups; -} + /******************************************************************************/ -/******************************************************************************/ + // Update color of matrix cells(s) + // Color changes when rules change -// helpers + function updateMatrixColors() { + var cells = uDom('.matrix .matRow.rw > .matCell').removeClass(), + i = cells.length, + cell, expandos; + while ( i-- ) { + cell = cells.nodeAt(i); + expandos = expandosFromNode(cell); + addCellClass(cell, expandos.hostname, expandos.reqType); + } + popupWasResized(); + } -function getTemporaryColor(hostname, type) { - return matrixSnapshot.rows[hostname].temporary[matrixSnapshot.headerIndices.get(type)]; -} + /******************************************************************************/ + + // Update behavior of matrix: + // - Whether a section is collapsible or not. It is collapsible if: + // - It has at least one subdomain AND + // - There is no explicit rule anywhere in the subdomain cells AND + // - It is not part of group 3 (blacklisted hostnames) + + function updateMatrixBehavior() { + matrixList = matrixList || uDom('#matList'); + var sections = matrixList.descendants('.matSection'); + var i = sections.length; + var section, subdomainRows, j, subdomainRow; + while ( i-- ) { + section = sections.at(i); + subdomainRows = section.descendants('.l2:not(.g4)'); + j = subdomainRows.length; + while ( j-- ) { + subdomainRow = subdomainRows.at(j); + subdomainRow.toggleClass('collapsible', subdomainRow.descendants('.t81,.t82').length === 0); + } + section.toggleClass('collapsible', subdomainRows.filter('.collapsible').length > 0); + } + } -function getPermanentColor(hostname, type) { - return matrixSnapshot.rows[hostname].permanent[matrixSnapshot.headerIndices.get(type)]; -} + /******************************************************************************/ -function addCellClass(cell, hostname, type) { - var cl = cell.classList; - cl.add('matCell'); - cl.add('t' + getTemporaryColor(hostname, type).toString(16)); - cl.add('p' + getPermanentColor(hostname, type).toString(16)); -} + // handle user interaction with filters -/******************************************************************************/ + function getCellAction(hostname, type, leaning) { + var temporaryColor = getTemporaryColor(hostname, type); + var hue = temporaryColor & 0x03; + // Special case: root toggle only between two states + if ( type === '*' && hostname === '*' ) { + return hue === Green ? 'blacklistMatrixCell' : 'whitelistMatrixCell'; + } + // When explicitly blocked/allowed, can only graylist + var saturation = temporaryColor & 0x80; + if ( saturation === Dark ) { + return 'graylistMatrixCell'; + } + return leaning === 'whitelisting' ? 'whitelistMatrixCell' : 'blacklistMatrixCell'; + } -// This is required for when we update the matrix while it is open: -// the user might have collapsed/expanded one or more domains, and we don't -// want to lose all his hardwork. - -function getCollapseState(domain) { - var states = getUISetting('popupCollapseSpecificDomains'); - if ( typeof states === 'object' && states[domain] !== undefined ) { - return states[domain]; - } - return matrixSnapshot.collapseAllDomains === true; -} - -function toggleCollapseState(elem) { - if ( elem.ancestors('#matHead.collapsible').length > 0 ) { - toggleMainCollapseState(elem); - } else { - toggleSpecificCollapseState(elem); - } - popupWasResized(); -} - -function toggleMainCollapseState(uelem) { - var matHead = uelem.ancestors('#matHead.collapsible').toggleClass('collapsed'); - var collapsed = matrixSnapshot.collapseAllDomains = matHead.hasClass('collapsed'); - uDom('#matList .matSection.collapsible').toggleClass('collapsed', collapsed); - setUserSetting('popupCollapseAllDomains', collapsed); - - var specificCollapseStates = getUISetting('popupCollapseSpecificDomains') || {}; - var domains = Object.keys(specificCollapseStates); - var i = domains.length; - var domain; - while ( i-- ) { - domain = domains[i]; - if ( specificCollapseStates[domain] === collapsed ) { - delete specificCollapseStates[domain]; + function handleFilter(button, leaning) { + // our parent cell knows who we are + var cell = button.ancestors('div.matCell'), + expandos = expandosFromNode(cell), + type = expandos.reqType, + desHostname = expandos.hostname; + // https://github.com/gorhill/uMatrix/issues/24 + // No hostname can happen -- like with blacklist meta row + if ( desHostname === '' ) { + return; } + var request = { + what: getCellAction(desHostname, type, leaning), + srcHostname: matrixSnapshot.scope, + desHostname: desHostname, + type: type + }; + vAPI.messaging.send('popup.js', request, updateMatrixSnapshot); } - setUISetting('popupCollapseSpecificDomains', specificCollapseStates); -} - -function toggleSpecificCollapseState(uelem) { - // Remember collapse state forever, but only if it is different - // from main collapse switch. - var section = uelem.ancestors('.matSection.collapsible').toggleClass('collapsed'), - domain = expandosFromNode(section).domain, - collapsed = section.hasClass('collapsed'), - mainCollapseState = matrixSnapshot.collapseAllDomains === true, - specificCollapseStates = getUISetting('popupCollapseSpecificDomains') || {}; - if ( collapsed !== mainCollapseState ) { - specificCollapseStates[domain] = collapsed; - setUISetting('popupCollapseSpecificDomains', specificCollapseStates); - } else if ( specificCollapseStates[domain] !== undefined ) { - delete specificCollapseStates[domain]; - setUISetting('popupCollapseSpecificDomains', specificCollapseStates); + + function handleWhitelistFilter(button) { + handleFilter(button, 'whitelisting'); } -} -/******************************************************************************/ + function handleBlacklistFilter(button) { + handleFilter(button, 'blacklisting'); + } -// Update count value of matrix cells(s) - -function updateMatrixCounts() { - var matCells = uDom('.matrix .matRow.rw > .matCell'), - i = matCells.length, - matRow, matCell, count, counts, - headerIndices = matrixSnapshot.headerIndices, - rows = matrixSnapshot.rows, - expandos; - while ( i-- ) { - matCell = matCells.nodeAt(i); - expandos = expandosFromNode(matCell); - if ( expandos.hostname === '*' || expandos.reqType === '*' ) { - continue; + /******************************************************************************/ + + var matrixRowPool = []; + var matrixSectionPool = []; + var matrixGroupPool = []; + var matrixRowTemplate = null; + var matrixList = null; + + var startMatrixUpdate = function() { + matrixList = matrixList || uDom('#matList'); + matrixList.detach(); + var rows = matrixList.descendants('.matRow'); + rows.detach(); + matrixRowPool = matrixRowPool.concat(rows.toArray()); + var sections = matrixList.descendants('.matSection'); + sections.detach(); + matrixSectionPool = matrixSectionPool.concat(sections.toArray()); + var groups = matrixList.descendants('.matGroup'); + groups.detach(); + matrixGroupPool = matrixGroupPool.concat(groups.toArray()); + }; + + var endMatrixUpdate = function() { + // https://github.com/gorhill/httpswitchboard/issues/246 + // If the matrix has no rows, we need to insert a dummy one, invisible, + // to ensure the extension pop-up is properly sized. This is needed because + // the header pane's `position` property is `fixed`, which means it doesn't + // affect layout size, hence the matrix header row will be truncated. + if ( matrixSnapshot.rowCount <= 1 ) { + matrixList.append(createMatrixRow().css('visibility', 'hidden')); } - matRow = matCell.parentNode; - counts = matRow.classList.contains('meta') ? 'totals' : 'counts'; - count = rows[expandos.hostname][counts][headerIndices.get(expandos.reqType)]; - if ( count === expandos.count ) { continue; } - expandos.count = count; - matCell.textContent = cellTextFromCount(count); - } -} + updateMatrixBehavior(); + matrixList.css('display', ''); + matrixList.appendTo('.paneContent'); + }; -function cellTextFromCount(count) { - if ( count === 0 ) { return '\u00A0'; } - if ( count < 100 ) { return count; } - return '99+'; -} + var createMatrixGroup = function() { + var group = matrixGroupPool.pop(); + if ( group ) { + return uDom(group).removeClass().addClass('matGroup'); + } + return uDom(document.createElement('div')).addClass('matGroup'); + }; -/******************************************************************************/ + var createMatrixSection = function() { + var section = matrixSectionPool.pop(); + if ( section ) { + return uDom(section).removeClass().addClass('matSection'); + } + return uDom(document.createElement('div')).addClass('matSection'); + }; + + var createMatrixRow = function() { + var row = matrixRowPool.pop(); + if ( row ) { + row.style.visibility = ''; + row = uDom(row); + row.descendants('.matCell').removeClass().addClass('matCell'); + row.removeClass().addClass('matRow'); + return row; + } + if ( matrixRowTemplate === null ) { + matrixRowTemplate = uDom('#templates .matRow'); + } + return matrixRowTemplate.clone(); + }; -// Update color of matrix cells(s) -// Color changes when rules change + /******************************************************************************/ -function updateMatrixColors() { - var cells = uDom('.matrix .matRow.rw > .matCell').removeClass(), - i = cells.length, - cell, expandos; - while ( i-- ) { - cell = cells.nodeAt(i); + function renderMatrixHeaderRow() { + var matHead = uDom('#matHead.collapsible'); + matHead.toggleClass('collapsed', matrixSnapshot.collapseAllDomains === true); + var cells = matHead.descendants('.matCell'), cell, expandos; + cell = cells.nodeAt(0); + expandos = expandosFromNode(cell); + expandos.reqType = '*'; + expandos.hostname = '*'; + addCellClass(cell, '*', '*'); + cell = cells.nodeAt(1); + expandos = expandosFromNode(cell); + expandos.reqType = 'cookie'; + expandos.hostname = '*'; + addCellClass(cell, '*', 'cookie'); + cell = cells.nodeAt(2); + expandos = expandosFromNode(cell); + expandos.reqType = 'css'; + expandos.hostname = '*'; + addCellClass(cell, '*', 'css'); + cell = cells.nodeAt(3); expandos = expandosFromNode(cell); - addCellClass(cell, expandos.hostname, expandos.reqType); + expandos.reqType = 'image'; + expandos.hostname = '*'; + addCellClass(cell, '*', 'image'); + cell = cells.nodeAt(4); + expandos = expandosFromNode(cell); + expandos.reqType = 'media'; + expandos.hostname = '*'; + addCellClass(cell, '*', 'media'); + cell = cells.nodeAt(5); + expandos = expandosFromNode(cell); + expandos.reqType = 'script'; + expandos.hostname = '*'; + addCellClass(cell, '*', 'script'); + cell = cells.nodeAt(6); + expandos = expandosFromNode(cell); + expandos.reqType = 'xhr'; + expandos.hostname = '*'; + addCellClass(cell, '*', 'xhr'); + cell = cells.nodeAt(7); + expandos = expandosFromNode(cell); + expandos.reqType = 'frame'; + expandos.hostname = '*'; + addCellClass(cell, '*', 'frame'); + cell = cells.nodeAt(8); + expandos = expandosFromNode(cell); + expandos.reqType = 'other'; + expandos.hostname = '*'; + addCellClass(cell, '*', 'other'); + uDom('#matHead .matRow').css('display', ''); } - popupWasResized(); -} -/******************************************************************************/ - -// Update behavior of matrix: -// - Whether a section is collapsible or not. It is collapsible if: -// - It has at least one subdomain AND -// - There is no explicit rule anywhere in the subdomain cells AND -// - It is not part of group 3 (blacklisted hostnames) - -function updateMatrixBehavior() { - matrixList = matrixList || uDom('#matList'); - var sections = matrixList.descendants('.matSection'); - var i = sections.length; - var section, subdomainRows, j, subdomainRow; - while ( i-- ) { - section = sections.at(i); - subdomainRows = section.descendants('.l2:not(.g4)'); - j = subdomainRows.length; - while ( j-- ) { - subdomainRow = subdomainRows.at(j); - subdomainRow.toggleClass('collapsible', subdomainRow.descendants('.t81,.t82').length === 0); - } - section.toggleClass('collapsible', subdomainRows.filter('.collapsible').length > 0); + /******************************************************************************/ + + function renderMatrixCellDomain(cell, domain) { + var expandos = expandosFromNode(cell); + expandos.hostname = domain; + expandos.reqType = '*'; + addCellClass(cell.nodeAt(0), domain, '*'); + var contents = cell.contents(); + contents.nodeAt(0).textContent = domain === '1st-party' ? + firstPartyLabel : + punycode.toUnicode(domain); + contents.nodeAt(1).textContent = ' '; } -} -/******************************************************************************/ + function renderMatrixCellSubdomain(cell, domain, subomain) { + var expandos = expandosFromNode(cell); + expandos.hostname = subomain; + expandos.reqType = '*'; + addCellClass(cell.nodeAt(0), subomain, '*'); + var contents = cell.contents(); + contents.nodeAt(0).textContent = punycode.toUnicode(subomain.slice(0, subomain.lastIndexOf(domain)-1)) + '.'; + contents.nodeAt(1).textContent = punycode.toUnicode(domain); + } -// handle user interaction with filters - -function getCellAction(hostname, type, leaning) { - var temporaryColor = getTemporaryColor(hostname, type); - var hue = temporaryColor & 0x03; - // Special case: root toggle only between two states - if ( type === '*' && hostname === '*' ) { - return hue === Green ? 'blacklistMatrixCell' : 'whitelistMatrixCell'; - } - // When explicitly blocked/allowed, can only graylist - var saturation = temporaryColor & 0x80; - if ( saturation === Dark ) { - return 'graylistMatrixCell'; - } - return leaning === 'whitelisting' ? 'whitelistMatrixCell' : 'blacklistMatrixCell'; -} - -function handleFilter(button, leaning) { - // our parent cell knows who we are - var cell = button.ancestors('div.matCell'), - expandos = expandosFromNode(cell), - type = expandos.reqType, - desHostname = expandos.hostname; - // https://github.com/gorhill/uMatrix/issues/24 - // No hostname can happen -- like with blacklist meta row - if ( desHostname === '' ) { - return; - } - var request = { - what: getCellAction(desHostname, type, leaning), - srcHostname: matrixSnapshot.scope, - desHostname: desHostname, - type: type - }; - vAPI.messaging.send('popup.js', request, updateMatrixSnapshot); -} + function renderMatrixMetaCellDomain(cell, domain) { + var expandos = expandosFromNode(cell); + expandos.hostname = domain; + expandos.reqType = '*'; + addCellClass(cell.nodeAt(0), domain, '*'); + var contents = cell.contents(); + contents.nodeAt(0).textContent = '\u2217.' + punycode.toUnicode(domain); + contents.nodeAt(1).textContent = ' '; + } -function handleWhitelistFilter(button) { - handleFilter(button, 'whitelisting'); -} + function renderMatrixCellType(cell, hostname, type, count) { + var node = cell.nodeAt(0), + expandos = expandosFromNode(node); + expandos.hostname = hostname; + expandos.reqType = type; + expandos.count = count; + addCellClass(node, hostname, type); + node.textContent = cellTextFromCount(count); + } -function handleBlacklistFilter(button) { - handleFilter(button, 'blacklisting'); -} + function renderMatrixCellTypes(cells, hostname, countName) { + var counts = matrixSnapshot.rows[hostname][countName]; + var headerIndices = matrixSnapshot.headerIndices; + renderMatrixCellType(cells.at(1), hostname, 'cookie', counts[headerIndices.get('cookie')]); + renderMatrixCellType(cells.at(2), hostname, 'css', counts[headerIndices.get('css')]); + renderMatrixCellType(cells.at(3), hostname, 'image', counts[headerIndices.get('image')]); + renderMatrixCellType(cells.at(4), hostname, 'media', counts[headerIndices.get('media')]); + renderMatrixCellType(cells.at(5), hostname, 'script', counts[headerIndices.get('script')]); + renderMatrixCellType(cells.at(6), hostname, 'xhr', counts[headerIndices.get('xhr')]); + renderMatrixCellType(cells.at(7), hostname, 'frame', counts[headerIndices.get('frame')]); + renderMatrixCellType(cells.at(8), hostname, 'other', counts[headerIndices.get('other')]); + } -/******************************************************************************/ + /******************************************************************************/ -var matrixRowPool = []; -var matrixSectionPool = []; -var matrixGroupPool = []; -var matrixRowTemplate = null; -var matrixList = null; - -var startMatrixUpdate = function() { - matrixList = matrixList || uDom('#matList'); - matrixList.detach(); - var rows = matrixList.descendants('.matRow'); - rows.detach(); - matrixRowPool = matrixRowPool.concat(rows.toArray()); - var sections = matrixList.descendants('.matSection'); - sections.detach(); - matrixSectionPool = matrixSectionPool.concat(sections.toArray()); - var groups = matrixList.descendants('.matGroup'); - groups.detach(); - matrixGroupPool = matrixGroupPool.concat(groups.toArray()); -}; - -var endMatrixUpdate = function() { - // https://github.com/gorhill/httpswitchboard/issues/246 - // If the matrix has no rows, we need to insert a dummy one, invisible, - // to ensure the extension pop-up is properly sized. This is needed because - // the header pane's `position` property is `fixed`, which means it doesn't - // affect layout size, hence the matrix header row will be truncated. - if ( matrixSnapshot.rowCount <= 1 ) { - matrixList.append(createMatrixRow().css('visibility', 'hidden')); - } - updateMatrixBehavior(); - matrixList.css('display', ''); - matrixList.appendTo('.paneContent'); -}; - -var createMatrixGroup = function() { - var group = matrixGroupPool.pop(); - if ( group ) { - return uDom(group).removeClass().addClass('matGroup'); - } - return uDom(document.createElement('div')).addClass('matGroup'); -}; - -var createMatrixSection = function() { - var section = matrixSectionPool.pop(); - if ( section ) { - return uDom(section).removeClass().addClass('matSection'); - } - return uDom(document.createElement('div')).addClass('matSection'); -}; - -var createMatrixRow = function() { - var row = matrixRowPool.pop(); - if ( row ) { - row.style.visibility = ''; - row = uDom(row); - row.descendants('.matCell').removeClass().addClass('matCell'); - row.removeClass().addClass('matRow'); - return row; - } - if ( matrixRowTemplate === null ) { - matrixRowTemplate = uDom('#templates .matRow'); - } - return matrixRowTemplate.clone(); -}; + function makeMatrixRowDomain(domain) { + var matrixRow = createMatrixRow().addClass('rw'); + var cells = matrixRow.descendants('.matCell'); + renderMatrixCellDomain(cells.at(0), domain); + renderMatrixCellTypes(cells, domain, 'counts'); + return matrixRow; + } -/******************************************************************************/ + function makeMatrixRowSubdomain(domain, subdomain) { + var matrixRow = createMatrixRow().addClass('rw'); + var cells = matrixRow.descendants('.matCell'); + renderMatrixCellSubdomain(cells.at(0), domain, subdomain); + renderMatrixCellTypes(cells, subdomain, 'counts'); + return matrixRow; + } -function renderMatrixHeaderRow() { - var matHead = uDom('#matHead.collapsible'); - matHead.toggleClass('collapsed', matrixSnapshot.collapseAllDomains === true); - var cells = matHead.descendants('.matCell'), cell, expandos; - cell = cells.nodeAt(0); - expandos = expandosFromNode(cell); - expandos.reqType = '*'; - expandos.hostname = '*'; - addCellClass(cell, '*', '*'); - cell = cells.nodeAt(1); - expandos = expandosFromNode(cell); - expandos.reqType = 'cookie'; - expandos.hostname = '*'; - addCellClass(cell, '*', 'cookie'); - cell = cells.nodeAt(2); - expandos = expandosFromNode(cell); - expandos.reqType = 'css'; - expandos.hostname = '*'; - addCellClass(cell, '*', 'css'); - cell = cells.nodeAt(3); - expandos = expandosFromNode(cell); - expandos.reqType = 'image'; - expandos.hostname = '*'; - addCellClass(cell, '*', 'image'); - cell = cells.nodeAt(4); - expandos = expandosFromNode(cell); - expandos.reqType = 'media'; - expandos.hostname = '*'; - addCellClass(cell, '*', 'media'); - cell = cells.nodeAt(5); - expandos = expandosFromNode(cell); - expandos.reqType = 'script'; - expandos.hostname = '*'; - addCellClass(cell, '*', 'script'); - cell = cells.nodeAt(6); - expandos = expandosFromNode(cell); - expandos.reqType = 'xhr'; - expandos.hostname = '*'; - addCellClass(cell, '*', 'xhr'); - cell = cells.nodeAt(7); - expandos = expandosFromNode(cell); - expandos.reqType = 'frame'; - expandos.hostname = '*'; - addCellClass(cell, '*', 'frame'); - cell = cells.nodeAt(8); - expandos = expandosFromNode(cell); - expandos.reqType = 'other'; - expandos.hostname = '*'; - addCellClass(cell, '*', 'other'); - uDom('#matHead .matRow').css('display', ''); -} + function makeMatrixMetaRowDomain(domain) { + var matrixRow = createMatrixRow().addClass('rw'); + var cells = matrixRow.descendants('.matCell'); + renderMatrixMetaCellDomain(cells.at(0), domain); + renderMatrixCellTypes(cells, domain, 'totals'); + return matrixRow; + } -/******************************************************************************/ + /******************************************************************************/ -function renderMatrixCellDomain(cell, domain) { - var expandos = expandosFromNode(cell); - expandos.hostname = domain; - expandos.reqType = '*'; - addCellClass(cell.nodeAt(0), domain, '*'); - var contents = cell.contents(); - contents.nodeAt(0).textContent = domain === '1st-party' ? - firstPartyLabel : - punycode.toUnicode(domain); - contents.nodeAt(1).textContent = ' '; -} - -function renderMatrixCellSubdomain(cell, domain, subomain) { - var expandos = expandosFromNode(cell); - expandos.hostname = subomain; - expandos.reqType = '*'; - addCellClass(cell.nodeAt(0), subomain, '*'); - var contents = cell.contents(); - contents.nodeAt(0).textContent = punycode.toUnicode(subomain.slice(0, subomain.lastIndexOf(domain)-1)) + '.'; - contents.nodeAt(1).textContent = punycode.toUnicode(domain); -} - -function renderMatrixMetaCellDomain(cell, domain) { - var expandos = expandosFromNode(cell); - expandos.hostname = domain; - expandos.reqType = '*'; - addCellClass(cell.nodeAt(0), domain, '*'); - var contents = cell.contents(); - contents.nodeAt(0).textContent = '\u2217.' + punycode.toUnicode(domain); - contents.nodeAt(1).textContent = ' '; -} - -function renderMatrixCellType(cell, hostname, type, count) { - var node = cell.nodeAt(0), - expandos = expandosFromNode(node); - expandos.hostname = hostname; - expandos.reqType = type; - expandos.count = count; - addCellClass(node, hostname, type); - node.textContent = cellTextFromCount(count); -} - -function renderMatrixCellTypes(cells, hostname, countName) { - var counts = matrixSnapshot.rows[hostname][countName]; - var headerIndices = matrixSnapshot.headerIndices; - renderMatrixCellType(cells.at(1), hostname, 'cookie', counts[headerIndices.get('cookie')]); - renderMatrixCellType(cells.at(2), hostname, 'css', counts[headerIndices.get('css')]); - renderMatrixCellType(cells.at(3), hostname, 'image', counts[headerIndices.get('image')]); - renderMatrixCellType(cells.at(4), hostname, 'media', counts[headerIndices.get('media')]); - renderMatrixCellType(cells.at(5), hostname, 'script', counts[headerIndices.get('script')]); - renderMatrixCellType(cells.at(6), hostname, 'xhr', counts[headerIndices.get('xhr')]); - renderMatrixCellType(cells.at(7), hostname, 'frame', counts[headerIndices.get('frame')]); - renderMatrixCellType(cells.at(8), hostname, 'other', counts[headerIndices.get('other')]); -} + function renderMatrixMetaCellType(cell, count) { + // https://github.com/gorhill/uMatrix/issues/24 + // Don't forget to reset cell properties + var node = cell.nodeAt(0), + expandos = expandosFromNode(node); + expandos.hostname = ''; + expandos.reqType = ''; + expandos.count = count; + cell.addClass('t1'); + node.textContent = cellTextFromCount(count); + } -/******************************************************************************/ + function makeMatrixMetaRow(totals) { + var headerIndices = matrixSnapshot.headerIndices, + matrixRow = createMatrixRow().at(0).addClass('ro'), + cells = matrixRow.descendants('.matCell'), + contents = cells.at(0).addClass('t81').contents(), + expandos = expandosFromNode(cells.nodeAt(0)); + expandos.hostname = ''; + expandos.reqType = '*'; + contents.nodeAt(0).textContent = ' '; + contents.nodeAt(1).textContent = blacklistedHostnamesLabel.replace( + '{{count}}', + totals[headerIndices.get('*')].toLocaleString() + ); + renderMatrixMetaCellType(cells.at(1), totals[headerIndices.get('cookie')]); + renderMatrixMetaCellType(cells.at(2), totals[headerIndices.get('css')]); + renderMatrixMetaCellType(cells.at(3), totals[headerIndices.get('image')]); + renderMatrixMetaCellType(cells.at(4), totals[headerIndices.get('media')]); + renderMatrixMetaCellType(cells.at(5), totals[headerIndices.get('script')]); + renderMatrixMetaCellType(cells.at(6), totals[headerIndices.get('xhr')]); + renderMatrixMetaCellType(cells.at(7), totals[headerIndices.get('frame')]); + renderMatrixMetaCellType(cells.at(8), totals[headerIndices.get('other')]); + return matrixRow; + } -function makeMatrixRowDomain(domain) { - var matrixRow = createMatrixRow().addClass('rw'); - var cells = matrixRow.descendants('.matCell'); - renderMatrixCellDomain(cells.at(0), domain); - renderMatrixCellTypes(cells, domain, 'counts'); - return matrixRow; -} - -function makeMatrixRowSubdomain(domain, subdomain) { - var matrixRow = createMatrixRow().addClass('rw'); - var cells = matrixRow.descendants('.matCell'); - renderMatrixCellSubdomain(cells.at(0), domain, subdomain); - renderMatrixCellTypes(cells, subdomain, 'counts'); - return matrixRow; -} - -function makeMatrixMetaRowDomain(domain) { - var matrixRow = createMatrixRow().addClass('rw'); - var cells = matrixRow.descendants('.matCell'); - renderMatrixMetaCellDomain(cells.at(0), domain); - renderMatrixCellTypes(cells, domain, 'totals'); - return matrixRow; -} + /******************************************************************************/ -/******************************************************************************/ + function computeMatrixGroupMetaStats(group) { + var headerIndices = matrixSnapshot.headerIndices, + anyTypeIndex = headerIndices.get('*'), + n = headerIndices.size, + totals = new Array(n), + i = n; + while ( i-- ) { + totals[i] = 0; + } + var rows = matrixSnapshot.rows, row; + for ( var hostname in rows ) { + if ( rows.hasOwnProperty(hostname) === false ) { + continue; + } + row = rows[hostname]; + if ( group.hasOwnProperty(row.domain) === false ) { + continue; + } + if ( row.counts[anyTypeIndex] === 0 ) { + continue; + } + totals[0] += 1; + for ( i = 1; i < n; i++ ) { + totals[i] += row.counts[i]; + } + } + return totals; + } -function renderMatrixMetaCellType(cell, count) { - // https://github.com/gorhill/uMatrix/issues/24 - // Don't forget to reset cell properties - var node = cell.nodeAt(0), - expandos = expandosFromNode(node); - expandos.hostname = ''; - expandos.reqType = ''; - expandos.count = count; - cell.addClass('t1'); - node.textContent = cellTextFromCount(count); -} - -function makeMatrixMetaRow(totals) { - var headerIndices = matrixSnapshot.headerIndices, - matrixRow = createMatrixRow().at(0).addClass('ro'), - cells = matrixRow.descendants('.matCell'), - contents = cells.at(0).addClass('t81').contents(), - expandos = expandosFromNode(cells.nodeAt(0)); - expandos.hostname = ''; - expandos.reqType = '*'; - contents.nodeAt(0).textContent = ' '; - contents.nodeAt(1).textContent = blacklistedHostnamesLabel.replace( - '{{count}}', - totals[headerIndices.get('*')].toLocaleString() - ); - renderMatrixMetaCellType(cells.at(1), totals[headerIndices.get('cookie')]); - renderMatrixMetaCellType(cells.at(2), totals[headerIndices.get('css')]); - renderMatrixMetaCellType(cells.at(3), totals[headerIndices.get('image')]); - renderMatrixMetaCellType(cells.at(4), totals[headerIndices.get('media')]); - renderMatrixMetaCellType(cells.at(5), totals[headerIndices.get('script')]); - renderMatrixMetaCellType(cells.at(6), totals[headerIndices.get('xhr')]); - renderMatrixMetaCellType(cells.at(7), totals[headerIndices.get('frame')]); - renderMatrixMetaCellType(cells.at(8), totals[headerIndices.get('other')]); - return matrixRow; -} + /******************************************************************************/ -/******************************************************************************/ + // Compare hostname helper, to order hostname in a logical manner: + // top-most < bottom-most, take into account whether IP address or + // named hostname -function computeMatrixGroupMetaStats(group) { - var headerIndices = matrixSnapshot.headerIndices, - anyTypeIndex = headerIndices.get('*'), - n = headerIndices.size, - totals = new Array(n), - i = n; - while ( i-- ) { - totals[i] = 0; - } - var rows = matrixSnapshot.rows, row; - for ( var hostname in rows ) { - if ( rows.hasOwnProperty(hostname) === false ) { - continue; + function hostnameCompare(a,b) { + // Normalize: most significant parts first + if ( !a.match(/^\d+(\.\d+){1,3}$/) ) { + var aa = a.split('.'); + a = aa.slice(-2).concat(aa.slice(0,-2).reverse()).join('.'); } - row = rows[hostname]; - if ( group.hasOwnProperty(row.domain) === false ) { - continue; + if ( !b.match(/^\d+(\.\d+){1,3}$/) ) { + var bb = b.split('.'); + b = bb.slice(-2).concat(bb.slice(0,-2).reverse()).join('.'); } - if ( row.counts[anyTypeIndex] === 0 ) { - continue; - } - totals[0] += 1; - for ( i = 1; i < n; i++ ) { - totals[i] += row.counts[i]; + return a.localeCompare(b); + } + + /******************************************************************************/ + + function makeMatrixGroup0SectionDomain() { + return makeMatrixRowDomain('1st-party').addClass('g0 l1'); + } + + function makeMatrixGroup0Section() { + var domainDiv = createMatrixSection(); + expandosFromNode(domainDiv).domain = '1st-party'; + makeMatrixGroup0SectionDomain().appendTo(domainDiv); + return domainDiv; + } + + function makeMatrixGroup0() { + // Show literal "1st-party" row only if there is + // at least one 1st-party hostname + if ( Object.keys(groupsSnapshot[1]).length === 0 ) { + return; } + var groupDiv = createMatrixGroup().addClass('g0'); + makeMatrixGroup0Section().appendTo(groupDiv); + groupDiv.appendTo(matrixList); } - return totals; -} -/******************************************************************************/ + /******************************************************************************/ -// Compare hostname helper, to order hostname in a logical manner: -// top-most < bottom-most, take into account whether IP address or -// named hostname + function makeMatrixGroup1SectionDomain(domain) { + return makeMatrixRowDomain(domain) + .addClass('g1 l1'); + } -function hostnameCompare(a,b) { - // Normalize: most significant parts first - if ( !a.match(/^\d+(\.\d+){1,3}$/) ) { - var aa = a.split('.'); - a = aa.slice(-2).concat(aa.slice(0,-2).reverse()).join('.'); + function makeMatrixGroup1SectionSubomain(domain, subdomain) { + return makeMatrixRowSubdomain(domain, subdomain) + .addClass('g1 l2'); } - if ( !b.match(/^\d+(\.\d+){1,3}$/) ) { - var bb = b.split('.'); - b = bb.slice(-2).concat(bb.slice(0,-2).reverse()).join('.'); + + function makeMatrixGroup1SectionMetaDomain(domain) { + return makeMatrixMetaRowDomain(domain).addClass('g1 l1 meta'); } - return a.localeCompare(b); -} -/******************************************************************************/ + function makeMatrixGroup1Section(hostnames) { + var domain = hostnames[0]; + var domainDiv = createMatrixSection() + .toggleClass('collapsed', getCollapseState(domain)); + expandosFromNode(domainDiv).domain = domain; + if ( hostnames.length > 1 ) { + makeMatrixGroup1SectionMetaDomain(domain) + .appendTo(domainDiv); + } + makeMatrixGroup1SectionDomain(domain) + .appendTo(domainDiv); + for ( var i = 1; i < hostnames.length; i++ ) { + makeMatrixGroup1SectionSubomain(domain, hostnames[i]) + .appendTo(domainDiv); + } + return domainDiv; + } -function makeMatrixGroup0SectionDomain() { - return makeMatrixRowDomain('1st-party').addClass('g0 l1'); -} + function makeMatrixGroup1(group) { + var domains = Object.keys(group).sort(hostnameCompare); + if ( domains.length ) { + var groupDiv = createMatrixGroup().addClass('g1'); + makeMatrixGroup1Section(Object.keys(group[domains[0]]).sort(hostnameCompare)) + .appendTo(groupDiv); + for ( var i = 1; i < domains.length; i++ ) { + makeMatrixGroup1Section(Object.keys(group[domains[i]]).sort(hostnameCompare)) + .appendTo(groupDiv); + } + groupDiv.appendTo(matrixList); + } + } -function makeMatrixGroup0Section() { - var domainDiv = createMatrixSection(); - expandosFromNode(domainDiv).domain = '1st-party'; - makeMatrixGroup0SectionDomain().appendTo(domainDiv); - return domainDiv; -} + /******************************************************************************/ -function makeMatrixGroup0() { - // Show literal "1st-party" row only if there is - // at least one 1st-party hostname - if ( Object.keys(groupsSnapshot[1]).length === 0 ) { - return; + function makeMatrixGroup2SectionDomain(domain) { + return makeMatrixRowDomain(domain) + .addClass('g2 l1'); } - var groupDiv = createMatrixGroup().addClass('g0'); - makeMatrixGroup0Section().appendTo(groupDiv); - groupDiv.appendTo(matrixList); -} -/******************************************************************************/ + function makeMatrixGroup2SectionSubomain(domain, subdomain) { + return makeMatrixRowSubdomain(domain, subdomain) + .addClass('g2 l2'); + } -function makeMatrixGroup1SectionDomain(domain) { - return makeMatrixRowDomain(domain) - .addClass('g1 l1'); -} - -function makeMatrixGroup1SectionSubomain(domain, subdomain) { - return makeMatrixRowSubdomain(domain, subdomain) - .addClass('g1 l2'); -} - -function makeMatrixGroup1SectionMetaDomain(domain) { - return makeMatrixMetaRowDomain(domain).addClass('g1 l1 meta'); -} - -function makeMatrixGroup1Section(hostnames) { - var domain = hostnames[0]; - var domainDiv = createMatrixSection() - .toggleClass('collapsed', getCollapseState(domain)); - expandosFromNode(domainDiv).domain = domain; - if ( hostnames.length > 1 ) { - makeMatrixGroup1SectionMetaDomain(domain) - .appendTo(domainDiv); + function makeMatrixGroup2SectionMetaDomain(domain) { + return makeMatrixMetaRowDomain(domain).addClass('g2 l1 meta'); } - makeMatrixGroup1SectionDomain(domain) - .appendTo(domainDiv); - for ( var i = 1; i < hostnames.length; i++ ) { - makeMatrixGroup1SectionSubomain(domain, hostnames[i]) + + function makeMatrixGroup2Section(hostnames) { + var domain = hostnames[0]; + var domainDiv = createMatrixSection() + .toggleClass('collapsed', getCollapseState(domain)); + expandosFromNode(domainDiv).domain = domain; + if ( hostnames.length > 1 ) { + makeMatrixGroup2SectionMetaDomain(domain).appendTo(domainDiv); + } + makeMatrixGroup2SectionDomain(domain) .appendTo(domainDiv); + for ( var i = 1; i < hostnames.length; i++ ) { + makeMatrixGroup2SectionSubomain(domain, hostnames[i]) + .appendTo(domainDiv); + } + return domainDiv; } - return domainDiv; -} -function makeMatrixGroup1(group) { - var domains = Object.keys(group).sort(hostnameCompare); - if ( domains.length ) { - var groupDiv = createMatrixGroup().addClass('g1'); - makeMatrixGroup1Section(Object.keys(group[domains[0]]).sort(hostnameCompare)) - .appendTo(groupDiv); - for ( var i = 1; i < domains.length; i++ ) { - makeMatrixGroup1Section(Object.keys(group[domains[i]]).sort(hostnameCompare)) + function makeMatrixGroup2(group) { + var domains = Object.keys(group).sort(hostnameCompare); + if ( domains.length) { + var groupDiv = createMatrixGroup() + .addClass('g2'); + makeMatrixGroup2Section(Object.keys(group[domains[0]]).sort(hostnameCompare)) .appendTo(groupDiv); + for ( var i = 1; i < domains.length; i++ ) { + makeMatrixGroup2Section(Object.keys(group[domains[i]]).sort(hostnameCompare)) + .appendTo(groupDiv); + } + groupDiv.appendTo(matrixList); } - groupDiv.appendTo(matrixList); } -} -/******************************************************************************/ + /******************************************************************************/ + + function makeMatrixGroup3SectionDomain(domain) { + return makeMatrixRowDomain(domain) + .addClass('g3 l1'); + } + + function makeMatrixGroup3SectionSubomain(domain, subdomain) { + return makeMatrixRowSubdomain(domain, subdomain) + .addClass('g3 l2'); + } -function makeMatrixGroup2SectionDomain(domain) { - return makeMatrixRowDomain(domain) - .addClass('g2 l1'); -} - -function makeMatrixGroup2SectionSubomain(domain, subdomain) { - return makeMatrixRowSubdomain(domain, subdomain) - .addClass('g2 l2'); -} - -function makeMatrixGroup2SectionMetaDomain(domain) { - return makeMatrixMetaRowDomain(domain).addClass('g2 l1 meta'); -} - -function makeMatrixGroup2Section(hostnames) { - var domain = hostnames[0]; - var domainDiv = createMatrixSection() - .toggleClass('collapsed', getCollapseState(domain)); - expandosFromNode(domainDiv).domain = domain; - if ( hostnames.length > 1 ) { - makeMatrixGroup2SectionMetaDomain(domain).appendTo(domainDiv); - } - makeMatrixGroup2SectionDomain(domain) - .appendTo(domainDiv); - for ( var i = 1; i < hostnames.length; i++ ) { - makeMatrixGroup2SectionSubomain(domain, hostnames[i]) + function makeMatrixGroup3SectionMetaDomain(domain) { + return makeMatrixMetaRowDomain(domain).addClass('g3 l1 meta'); + } + + function makeMatrixGroup3Section(hostnames) { + var domain = hostnames[0]; + var domainDiv = createMatrixSection() + .toggleClass('collapsed', getCollapseState(domain)); + expandosFromNode(domainDiv).domain = domain; + if ( hostnames.length > 1 ) { + makeMatrixGroup3SectionMetaDomain(domain).appendTo(domainDiv); + } + makeMatrixGroup3SectionDomain(domain) .appendTo(domainDiv); + for ( var i = 1; i < hostnames.length; i++ ) { + makeMatrixGroup3SectionSubomain(domain, hostnames[i]) + .appendTo(domainDiv); + } + return domainDiv; } - return domainDiv; -} -function makeMatrixGroup2(group) { - var domains = Object.keys(group).sort(hostnameCompare); - if ( domains.length) { - var groupDiv = createMatrixGroup() - .addClass('g2'); - makeMatrixGroup2Section(Object.keys(group[domains[0]]).sort(hostnameCompare)) - .appendTo(groupDiv); - for ( var i = 1; i < domains.length; i++ ) { - makeMatrixGroup2Section(Object.keys(group[domains[i]]).sort(hostnameCompare)) + function makeMatrixGroup3(group) { + var domains = Object.keys(group).sort(hostnameCompare); + if ( domains.length) { + var groupDiv = createMatrixGroup() + .addClass('g3'); + makeMatrixGroup3Section(Object.keys(group[domains[0]]).sort(hostnameCompare)) .appendTo(groupDiv); + for ( var i = 1; i < domains.length; i++ ) { + makeMatrixGroup3Section(Object.keys(group[domains[i]]).sort(hostnameCompare)) + .appendTo(groupDiv); + } + groupDiv.appendTo(matrixList); } - groupDiv.appendTo(matrixList); } -} -/******************************************************************************/ + /******************************************************************************/ -function makeMatrixGroup3SectionDomain(domain) { - return makeMatrixRowDomain(domain) - .addClass('g3 l1'); -} - -function makeMatrixGroup3SectionSubomain(domain, subdomain) { - return makeMatrixRowSubdomain(domain, subdomain) - .addClass('g3 l2'); -} - -function makeMatrixGroup3SectionMetaDomain(domain) { - return makeMatrixMetaRowDomain(domain).addClass('g3 l1 meta'); -} - -function makeMatrixGroup3Section(hostnames) { - var domain = hostnames[0]; - var domainDiv = createMatrixSection() - .toggleClass('collapsed', getCollapseState(domain)); - expandosFromNode(domainDiv).domain = domain; - if ( hostnames.length > 1 ) { - makeMatrixGroup3SectionMetaDomain(domain).appendTo(domainDiv); - } - makeMatrixGroup3SectionDomain(domain) - .appendTo(domainDiv); - for ( var i = 1; i < hostnames.length; i++ ) { - makeMatrixGroup3SectionSubomain(domain, hostnames[i]) + function makeMatrixGroup4SectionDomain(domain) { + return makeMatrixRowDomain(domain) + .addClass('g4 l1'); + } + + function makeMatrixGroup4SectionSubomain(domain, subdomain) { + return makeMatrixRowSubdomain(domain, subdomain) + .addClass('g4 l2'); + } + + function makeMatrixGroup4Section(hostnames) { + var domain = hostnames[0]; + var domainDiv = createMatrixSection(); + expandosFromNode(domainDiv).domain = domain; + makeMatrixGroup4SectionDomain(domain) .appendTo(domainDiv); + for ( var i = 1; i < hostnames.length; i++ ) { + makeMatrixGroup4SectionSubomain(domain, hostnames[i]) + .appendTo(domainDiv); + } + return domainDiv; } - return domainDiv; -} -function makeMatrixGroup3(group) { - var domains = Object.keys(group).sort(hostnameCompare); - if ( domains.length) { - var groupDiv = createMatrixGroup() - .addClass('g3'); - makeMatrixGroup3Section(Object.keys(group[domains[0]]).sort(hostnameCompare)) + function makeMatrixGroup4(group) { + var domains = Object.keys(group).sort(hostnameCompare); + if ( domains.length === 0 ) { + return; + } + var groupDiv = createMatrixGroup().addClass('g4'); + createMatrixSection() + .addClass('g4Meta') + .toggleClass('g4Collapsed', !!matrixSnapshot.collapseBlacklistedDomains) + .appendTo(groupDiv); + makeMatrixMetaRow(computeMatrixGroupMetaStats(group), 'g4') + .appendTo(groupDiv); + makeMatrixGroup4Section(Object.keys(group[domains[0]]).sort(hostnameCompare)) .appendTo(groupDiv); for ( var i = 1; i < domains.length; i++ ) { - makeMatrixGroup3Section(Object.keys(group[domains[i]]).sort(hostnameCompare)) + makeMatrixGroup4Section(Object.keys(group[domains[i]]).sort(hostnameCompare)) .appendTo(groupDiv); } groupDiv.appendTo(matrixList); } -} -/******************************************************************************/ + /******************************************************************************/ -function makeMatrixGroup4SectionDomain(domain) { - return makeMatrixRowDomain(domain) - .addClass('g4 l1'); -} - -function makeMatrixGroup4SectionSubomain(domain, subdomain) { - return makeMatrixRowSubdomain(domain, subdomain) - .addClass('g4 l2'); -} - -function makeMatrixGroup4Section(hostnames) { - var domain = hostnames[0]; - var domainDiv = createMatrixSection(); - expandosFromNode(domainDiv).domain = domain; - makeMatrixGroup4SectionDomain(domain) - .appendTo(domainDiv); - for ( var i = 1; i < hostnames.length; i++ ) { - makeMatrixGroup4SectionSubomain(domain, hostnames[i]) - .appendTo(domainDiv); - } - return domainDiv; -} - -function makeMatrixGroup4(group) { - var domains = Object.keys(group).sort(hostnameCompare); - if ( domains.length === 0 ) { - return; - } - var groupDiv = createMatrixGroup().addClass('g4'); - createMatrixSection() - .addClass('g4Meta') - .toggleClass('g4Collapsed', !!matrixSnapshot.collapseBlacklistedDomains) - .appendTo(groupDiv); - makeMatrixMetaRow(computeMatrixGroupMetaStats(group), 'g4') - .appendTo(groupDiv); - makeMatrixGroup4Section(Object.keys(group[domains[0]]).sort(hostnameCompare)) - .appendTo(groupDiv); - for ( var i = 1; i < domains.length; i++ ) { - makeMatrixGroup4Section(Object.keys(group[domains[i]]).sort(hostnameCompare)) - .appendTo(groupDiv); - } - groupDiv.appendTo(matrixList); -} + var makeMenu = function() { + var groupStats = getGroupStats(); -/******************************************************************************/ + if ( Object.keys(groupStats).length === 0 ) { return; } -var makeMenu = function() { - var groupStats = getGroupStats(); + // https://github.com/gorhill/httpswitchboard/issues/31 + if ( matrixCellHotspots ) { + matrixCellHotspots.detach(); + } - if ( Object.keys(groupStats).length === 0 ) { return; } + renderMatrixHeaderRow(); - // https://github.com/gorhill/httpswitchboard/issues/31 - if ( matrixCellHotspots ) { - matrixCellHotspots.detach(); + startMatrixUpdate(); + makeMatrixGroup0(groupStats[0]); + makeMatrixGroup1(groupStats[1]); + makeMatrixGroup2(groupStats[2]); + makeMatrixGroup3(groupStats[3]); + makeMatrixGroup4(groupStats[4]); + endMatrixUpdate(); + + initScopeCell(); + updateMatrixButtons(); + resizePopup(); + }; + + /******************************************************************************/ + + // Do all the stuff that needs to be done before building menu et al. + + function initMenuEnvironment() { + document.body.style.setProperty( + 'font-size', + getUserSetting('displayTextSize') + ); + document.body.classList.toggle( + 'colorblind', + getUserSetting('colorBlindFriendly') + ); + uDom.nodeFromId('version').textContent = matrixSnapshot.appVersion || ''; + + var prettyNames = matrixHeaderPrettyNames; + var keys = Object.keys(prettyNames); + var i = keys.length; + var cell, key, text; + while ( i-- ) { + key = keys[i]; + cell = uDom('#matHead .matCell[data-req-type="'+ key +'"]'); + text = vAPI.i18n(key + 'PrettyName'); + cell.text(text); + prettyNames[key] = text; + } + + firstPartyLabel = uDom('[data-i18n="matrix1stPartyLabel"]').text(); + blacklistedHostnamesLabel = uDom('[data-i18n="matrixBlacklistedHostnames"]').text(); } - renderMatrixHeaderRow(); + /******************************************************************************/ - startMatrixUpdate(); - makeMatrixGroup0(groupStats[0]); - makeMatrixGroup1(groupStats[1]); - makeMatrixGroup2(groupStats[2]); - makeMatrixGroup3(groupStats[3]); - makeMatrixGroup4(groupStats[4]); - endMatrixUpdate(); + // Create page scopes for the web page - initScopeCell(); - updateMatrixButtons(); - resizePopup(); -}; + function selectGlobalScope() { + if ( matrixSnapshot.scope === '*' ) { return; } + matrixSnapshot.scope = '*'; + document.body.classList.add('globalScope'); + matrixSnapshot.tMatrixModifiedTime = undefined; + updateMatrixSnapshot(); + dropDownMenuHide(); + } -/******************************************************************************/ + function selectSpecificScope(ev) { + var newScope = ev.target.getAttribute('data-scope'); + if ( !newScope || matrixSnapshot.scope === newScope ) { return; } + document.body.classList.remove('globalScope'); + matrixSnapshot.scope = newScope; + matrixSnapshot.tMatrixModifiedTime = undefined; + updateMatrixSnapshot(); + dropDownMenuHide(); + } -// Do all the stuff that needs to be done before building menu et al. - -function initMenuEnvironment() { - document.body.style.setProperty( - 'font-size', - getUserSetting('displayTextSize') - ); - document.body.classList.toggle( - 'colorblind', - getUserSetting('colorBlindFriendly') - ); - uDom.nodeFromId('version').textContent = matrixSnapshot.appVersion || ''; - - var prettyNames = matrixHeaderPrettyNames; - var keys = Object.keys(prettyNames); - var i = keys.length; - var cell, key, text; - while ( i-- ) { - key = keys[i]; - cell = uDom('#matHead .matCell[data-req-type="'+ key +'"]'); - text = vAPI.i18n(key + 'PrettyName'); - cell.text(text); - prettyNames[key] = text; - } - - firstPartyLabel = uDom('[data-i18n="matrix1stPartyLabel"]').text(); - blacklistedHostnamesLabel = uDom('[data-i18n="matrixBlacklistedHostnames"]').text(); -} + function initScopeCell() { + // It's possible there is no page URL at this point: some pages cannot + // be filtered by uMatrix. + if ( matrixSnapshot.url === '' ) { return; } + var specificScope = uDom.nodeFromId('specificScope'); -/******************************************************************************/ + while ( specificScope.firstChild !== null ) { + specificScope.removeChild(specificScope.firstChild); + } -// Create page scopes for the web page - -function selectGlobalScope() { - if ( matrixSnapshot.scope === '*' ) { return; } - matrixSnapshot.scope = '*'; - document.body.classList.add('globalScope'); - matrixSnapshot.tMatrixModifiedTime = undefined; - updateMatrixSnapshot(); - dropDownMenuHide(); -} - -function selectSpecificScope(ev) { - var newScope = ev.target.getAttribute('data-scope'); - if ( !newScope || matrixSnapshot.scope === newScope ) { return; } - document.body.classList.remove('globalScope'); - matrixSnapshot.scope = newScope; - matrixSnapshot.tMatrixModifiedTime = undefined; - updateMatrixSnapshot(); - dropDownMenuHide(); -} - -function initScopeCell() { - // It's possible there is no page URL at this point: some pages cannot - // be filtered by uMatrix. - if ( matrixSnapshot.url === '' ) { return; } - var specificScope = uDom.nodeFromId('specificScope'); - - while ( specificScope.firstChild !== null ) { - specificScope.removeChild(specificScope.firstChild); - } - - // Fill in the scope menu entries - var pos = matrixSnapshot.domain.indexOf('.'); - var tld, labels; - if ( pos === -1 ) { - tld = ''; - labels = matrixSnapshot.hostname; - } else { - tld = matrixSnapshot.domain.slice(pos + 1); - labels = matrixSnapshot.hostname.slice(0, -tld.length); - } - var beg = 0, span, label; - while ( beg < labels.length ) { - pos = labels.indexOf('.', beg); + // Fill in the scope menu entries + var pos = matrixSnapshot.domain.indexOf('.'); + var tld, labels; if ( pos === -1 ) { - pos = labels.length; + tld = ''; + labels = matrixSnapshot.hostname; } else { - pos += 1; + tld = matrixSnapshot.domain.slice(pos + 1); + labels = matrixSnapshot.hostname.slice(0, -tld.length); } - label = document.createElement('span'); - label.appendChild( - document.createTextNode(punycode.toUnicode(labels.slice(beg, pos))) - ); - span = document.createElement('span'); - span.setAttribute('data-scope', labels.slice(beg) + tld); - span.appendChild(label); - specificScope.appendChild(span); - beg = pos; - } - if ( tld !== '' ) { - label = document.createElement('span'); - label.appendChild(document.createTextNode(punycode.toUnicode(tld))); - span = document.createElement('span'); - span.setAttribute('data-scope', tld); - span.appendChild(label); - specificScope.appendChild(span); - } - updateScopeCell(); -} - -function updateScopeCell() { - var specificScope = uDom.nodeFromId('specificScope'), - isGlobal = matrixSnapshot.scope === '*'; - document.body.classList.toggle('globalScope', isGlobal); - specificScope.classList.toggle('on', !isGlobal); - uDom.nodeFromId('globalScope').classList.toggle('on', isGlobal); - for ( var node of specificScope.children ) { - node.classList.toggle( - 'on', - !isGlobal && - matrixSnapshot.scope.endsWith(node.getAttribute('data-scope')) - ); + var beg = 0, span, label; + while ( beg < labels.length ) { + pos = labels.indexOf('.', beg); + if ( pos === -1 ) { + pos = labels.length; + } else { + pos += 1; + } + label = document.createElement('span'); + label.appendChild( + document.createTextNode(punycode.toUnicode(labels.slice(beg, pos))) + ); + span = document.createElement('span'); + span.setAttribute('data-scope', labels.slice(beg) + tld); + span.appendChild(label); + specificScope.appendChild(span); + beg = pos; + } + if ( tld !== '' ) { + label = document.createElement('span'); + label.appendChild(document.createTextNode(punycode.toUnicode(tld))); + span = document.createElement('span'); + span.setAttribute('data-scope', tld); + span.appendChild(label); + specificScope.appendChild(span); + } + updateScopeCell(); } -} -/******************************************************************************/ - -function updateMatrixSwitches() { - var count = 0, - enabled, - switches = matrixSnapshot.tSwitches; - for ( var switchName in switches ) { - if ( switches.hasOwnProperty(switchName) === false ) { continue; } - enabled = switches[switchName]; - if ( enabled && switchName !== 'matrix-off' ) { - count += 1; + function updateScopeCell() { + var specificScope = uDom.nodeFromId('specificScope'), + isGlobal = matrixSnapshot.scope === '*'; + document.body.classList.toggle('globalScope', isGlobal); + specificScope.classList.toggle('on', !isGlobal); + uDom.nodeFromId('globalScope').classList.toggle('on', isGlobal); + for ( var node of specificScope.children ) { + node.classList.toggle( + 'on', + !isGlobal && + matrixSnapshot.scope.endsWith(node.getAttribute('data-scope')) + ); } - uDom('#mtxSwitch_' + switchName).toggleClass('switchTrue', enabled); - } - uDom.nodeFromId('mtxSwitch_https-strict').classList.toggle( - 'relevant', - matrixSnapshot.hasMixedContent - ); - uDom.nodeFromId('mtxSwitch_no-workers').classList.toggle( - 'relevant', - matrixSnapshot.hasWebWorkers - ); - uDom.nodeFromId('mtxSwitch_referrer-spoof').classList.toggle( - 'relevant', - matrixSnapshot.has3pReferrer - ); - uDom.nodeFromId('mtxSwitch_noscript-spoof').classList.toggle( - 'relevant', - matrixSnapshot.hasNoscriptTags - ); - uDom.nodeFromSelector('#buttonMtxSwitches span.badge').textContent = - count.toLocaleString(); - uDom.nodeFromSelector('#mtxSwitch_matrix-off span.badge').textContent = - matrixSnapshot.blockedCount.toLocaleString(); - document.body.classList.toggle('powerOff', switches['matrix-off']); -} - -function toggleMatrixSwitch(ev) { - if ( ev.target.localName === 'a' ) { return; } - var elem = ev.currentTarget; - var pos = elem.id.indexOf('_'); - if ( pos === -1 ) { return; } - var switchName = elem.id.slice(pos + 1); - var request = { - what: 'toggleMatrixSwitch', - switchName: switchName, - srcHostname: matrixSnapshot.scope - }; - vAPI.messaging.send('popup.js', request, updateMatrixSnapshot); -} - -/******************************************************************************/ - -function updatePersistButton() { - var diffCount = matrixSnapshot.diff.length; - var button = uDom('#buttonPersist'); - button.contents() - .filter(function(){return this.nodeType===3;}) - .first() - .text(diffCount > 0 ? '\uf13e' : '\uf023'); - button.descendants('span.badge').text(diffCount > 0 ? diffCount : ''); - var disabled = diffCount === 0; - button.toggleClass('disabled', disabled); - uDom('#buttonRevertScope').toggleClass('disabled', disabled); -} - -/******************************************************************************/ - -function persistMatrix() { - var request = { - what: 'applyDiffToPermanentMatrix', - diff: matrixSnapshot.diff - }; - vAPI.messaging.send('popup.js', request, updateMatrixSnapshot); -} + } -/******************************************************************************/ + /******************************************************************************/ + + function updateMatrixSwitches() { + var count = 0, + enabled, + switches = matrixSnapshot.tSwitches; + for ( var switchName in switches ) { + if ( switches.hasOwnProperty(switchName) === false ) { continue; } + enabled = switches[switchName]; + if ( enabled && switchName !== 'matrix-off' ) { + count += 1; + } + uDom('#mtxSwitch_' + switchName).toggleClass('switchTrue', enabled); + } + uDom.nodeFromId('mtxSwitch_https-strict').classList.toggle( + 'relevant', + matrixSnapshot.hasMixedContent + ); + uDom.nodeFromId('mtxSwitch_no-workers').classList.toggle( + 'relevant', + matrixSnapshot.hasWebWorkers + ); + uDom.nodeFromId('mtxSwitch_referrer-spoof').classList.toggle( + 'relevant', + matrixSnapshot.has3pReferrer + ); + uDom.nodeFromId('mtxSwitch_noscript-spoof').classList.toggle( + 'relevant', + matrixSnapshot.hasNoscriptTags + ); + uDom.nodeFromSelector('#buttonMtxSwitches span.badge').textContent = + count.toLocaleString(); + uDom.nodeFromSelector('#mtxSwitch_matrix-off span.badge').textContent = + matrixSnapshot.blockedCount.toLocaleString(); + document.body.classList.toggle('powerOff', switches['matrix-off']); + } -// rhill 2014-03-12: revert completely ALL changes related to the -// current page, including scopes. + function toggleMatrixSwitch(ev) { + if ( ev.target.localName === 'a' ) { return; } + var elem = ev.currentTarget; + var pos = elem.id.indexOf('_'); + if ( pos === -1 ) { return; } + var switchName = elem.id.slice(pos + 1); + var request = { + what: 'toggleMatrixSwitch', + switchName: switchName, + srcHostname: matrixSnapshot.scope + }; + vAPI.messaging.send('popup.js', request, updateMatrixSnapshot); + } -function revertMatrix() { - var request = { - what: 'applyDiffToTemporaryMatrix', - diff: matrixSnapshot.diff - }; - vAPI.messaging.send('popup.js', request, updateMatrixSnapshot); -} + /******************************************************************************/ + + function updatePersistButton() { + var diffCount = matrixSnapshot.diff.length; + var button = uDom('#buttonPersist'); + button.contents() + .filter(function(){return this.nodeType===3;}) + .first() + .text(diffCount > 0 ? '\uf13e' : '\uf023'); + button.descendants('span.badge').text(diffCount > 0 ? diffCount : ''); + var disabled = diffCount === 0; + button.toggleClass('disabled', disabled); + uDom('#buttonRevertScope').toggleClass('disabled', disabled); + } -/******************************************************************************/ + /******************************************************************************/ -// Buttons which are affected by any changes in the matrix + function persistMatrix() { + var request = { + what: 'applyDiffToPermanentMatrix', + diff: matrixSnapshot.diff + }; + vAPI.messaging.send('popup.js', request, updateMatrixSnapshot); + } -function updateMatrixButtons() { - updateScopeCell(); - updateMatrixSwitches(); - updatePersistButton(); -} + /******************************************************************************/ -/******************************************************************************/ + // rhill 2014-03-12: revert completely ALL changes related to the + // current page, including scopes. -function revertAll() { - var request = { - what: 'revertTemporaryMatrix' - }; - vAPI.messaging.send('popup.js', request, updateMatrixSnapshot); - dropDownMenuHide(); -} + function revertMatrix() { + var request = { + what: 'applyDiffToTemporaryMatrix', + diff: matrixSnapshot.diff + }; + vAPI.messaging.send('popup.js', request, updateMatrixSnapshot); + } -/******************************************************************************/ + /******************************************************************************/ -function buttonReloadHandler(ev) { - vAPI.messaging.send('popup.js', { - what: 'forceReloadTab', - tabId: matrixSnapshot.tabId, - bypassCache: ev.ctrlKey || ev.metaKey || ev.shiftKey - }); -} + // Buttons which are affected by any changes in the matrix -/******************************************************************************/ + function updateMatrixButtons() { + updateScopeCell(); + updateMatrixSwitches(); + updatePersistButton(); + } -function mouseenterMatrixCellHandler(ev) { - matrixCellHotspots.appendTo(ev.target); -} + /******************************************************************************/ -function mouseleaveMatrixCellHandler() { - matrixCellHotspots.detach(); -} + function revertAll() { + var request = { + what: 'revertTemporaryMatrix' + }; + vAPI.messaging.send('popup.js', request, updateMatrixSnapshot); + dropDownMenuHide(); + } -/******************************************************************************/ + /******************************************************************************/ -function gotoExtensionURL(ev) { - var url = uDom(ev.currentTarget).attr('data-extension-url'); - if ( url ) { + function buttonReloadHandler(ev) { vAPI.messaging.send('popup.js', { - what: 'gotoExtensionURL', - url: url, - shiftKey: ev.shiftKey + what: 'forceReloadTab', + tabId: matrixSnapshot.tabId, + bypassCache: ev.ctrlKey || ev.metaKey || ev.shiftKey }); } - dropDownMenuHide(); - vAPI.closePopup(); -} - -/******************************************************************************/ -function dropDownMenuShow(ev) { - var button = ev.target; - var menuOverlay = document.getElementById(button.getAttribute('data-dropdown-menu')); - var butnRect = button.getBoundingClientRect(); - var viewRect = document.body.getBoundingClientRect(); - var butnNormalLeft = butnRect.left / (viewRect.width - butnRect.width); - menuOverlay.classList.add('show'); - var menu = menuOverlay.querySelector('.dropdown-menu'); - var menuRect = menu.getBoundingClientRect(); - var menuLeft = butnNormalLeft * (viewRect.width - menuRect.width); - menu.style.left = menuLeft.toFixed(0) + 'px'; - menu.style.top = butnRect.bottom + 'px'; -} - -function dropDownMenuHide() { - uDom('.dropdown-menu-capture').removeClass('show'); -} + /******************************************************************************/ -/******************************************************************************/ - -var onMatrixSnapshotReady = function(response) { - if ( response === 'ENOTFOUND' ) { - uDom.nodeFromId('noTabFound').textContent = - vAPI.i18n('matrixNoTabFound'); - document.body.classList.add('noTabFound'); - return; + function mouseenterMatrixCellHandler(ev) { + matrixCellHotspots.appendTo(ev.target); } - // Now that tabId and pageURL are set, we can build our menu - initMenuEnvironment(); - makeMenu(); + function mouseleaveMatrixCellHandler() { + matrixCellHotspots.detach(); + } - // After popup menu is built, check whether there is a non-empty matrix - if ( matrixSnapshot.url === '' ) { - uDom('#matHead').remove(); - uDom('#toolbarContainer').remove(); + /******************************************************************************/ - // https://github.com/gorhill/httpswitchboard/issues/191 - uDom('#noNetTrafficPrompt').text(vAPI.i18n('matrixNoNetTrafficPrompt')); - uDom('#noNetTrafficPrompt').css('display', ''); + function gotoExtensionURL(ev) { + var url = uDom(ev.currentTarget).attr('data-extension-url'); + if ( url ) { + vAPI.messaging.send('popup.js', { + what: 'gotoExtensionURL', + url: url, + shiftKey: ev.shiftKey + }); + } + dropDownMenuHide(); + vAPI.closePopup(); } - // Create a hash to find out whether the reload button needs to be - // highlighted. - // TODO: -}; + /******************************************************************************/ + + function dropDownMenuShow(ev) { + var button = ev.target; + var menuOverlay = document.getElementById(button.getAttribute('data-dropdown-menu')); + var butnRect = button.getBoundingClientRect(); + var viewRect = document.body.getBoundingClientRect(); + var butnNormalLeft = butnRect.left / (viewRect.width - butnRect.width); + menuOverlay.classList.add('show'); + var menu = menuOverlay.querySelector('.dropdown-menu'); + var menuRect = menu.getBoundingClientRect(); + var menuLeft = butnNormalLeft * (viewRect.width - menuRect.width); + menu.style.left = menuLeft.toFixed(0) + 'px'; + menu.style.top = butnRect.bottom + 'px'; + } -/******************************************************************************/ + function dropDownMenuHide() { + uDom('.dropdown-menu-capture').removeClass('show'); + } -var matrixSnapshotPoller = (function() { - var timer = null; + /******************************************************************************/ - var preprocessMatrixSnapshot = function(snapshot) { - if ( Array.isArray(snapshot.headerIndices) ) { - snapshot.headerIndices = new Map(snapshot.headerIndices); - } - return snapshot; - }; - - var processPollResult = function(response) { - if ( typeof response !== 'object' ) { - return; - } - if ( - response.mtxContentModified === false && - response.mtxCountModified === false && - response.pMatrixModified === false && - response.tMatrixModified === false - ) { + var onMatrixSnapshotReady = function(response) { + if ( response === 'ENOTFOUND' ) { + uDom.nodeFromId('noTabFound').textContent = + vAPI.i18n('matrixNoTabFound'); + document.body.classList.add('noTabFound'); return; } - matrixSnapshot = preprocessMatrixSnapshot(response); - if ( response.mtxContentModified ) { - makeMenu(); - return; - } - if ( response.mtxCountModified ) { - updateMatrixCounts(); - } - if ( - response.pMatrixModified || - response.tMatrixModified || - response.scopeModified - ) { - updateMatrixColors(); - updateMatrixBehavior(); - updateMatrixButtons(); + // Now that tabId and pageURL are set, we can build our menu + initMenuEnvironment(); + makeMenu(); + + // After popup menu is built, check whether there is a non-empty matrix + if ( matrixSnapshot.url === '' ) { + uDom('#matHead').remove(); + uDom('#toolbarContainer').remove(); + + // https://github.com/gorhill/httpswitchboard/issues/191 + uDom('#noNetTrafficPrompt').text(vAPI.i18n('matrixNoNetTrafficPrompt')); + uDom('#noNetTrafficPrompt').css('display', ''); } - }; - var onPolled = function(response) { - processPollResult(response); - pollAsync(); + // Create a hash to find out whether the reload button needs to be + // highlighted. + // TODO: }; - var pollNow = function() { - unpollAsync(); - vAPI.messaging.send('popup.js', { - what: 'matrixSnapshot', - tabId: matrixSnapshot.tabId, - scope: matrixSnapshot.scope, - mtxContentModifiedTime: matrixSnapshot.mtxContentModifiedTime, - mtxCountModifiedTime: matrixSnapshot.mtxCountModifiedTime, - mtxDiffCount: matrixSnapshot.diff.length, - pMatrixModifiedTime: matrixSnapshot.pMatrixModifiedTime, - tMatrixModifiedTime: matrixSnapshot.tMatrixModifiedTime, - }, onPolled); - }; + /******************************************************************************/ - var poll = function() { - timer = null; - pollNow(); - }; + var matrixSnapshotPoller = (function() { + var timer = null; - var pollAsync = function() { - if ( timer !== null ) { - return; - } - if ( document.defaultView === null ) { - return; - } - timer = vAPI.setTimeout(poll, 1414); - }; + var preprocessMatrixSnapshot = function(snapshot) { + if ( Array.isArray(snapshot.headerIndices) ) { + snapshot.headerIndices = new Map(snapshot.headerIndices); + } + return snapshot; + }; + + var processPollResult = function(response) { + if ( typeof response !== 'object' ) { + return; + } + if ( + response.mtxContentModified === false && + response.mtxCountModified === false && + response.pMatrixModified === false && + response.tMatrixModified === false + ) { + return; + } + matrixSnapshot = preprocessMatrixSnapshot(response); + + if ( response.mtxContentModified ) { + makeMenu(); + return; + } + if ( response.mtxCountModified ) { + updateMatrixCounts(); + } + if ( + response.pMatrixModified || + response.tMatrixModified || + response.scopeModified + ) { + updateMatrixColors(); + updateMatrixBehavior(); + updateMatrixButtons(); + } + }; + + var onPolled = function(response) { + processPollResult(response); + pollAsync(); + }; + + var pollNow = function() { + unpollAsync(); + vAPI.messaging.send('popup.js', { + what: 'matrixSnapshot', + tabId: matrixSnapshot.tabId, + scope: matrixSnapshot.scope, + mtxContentModifiedTime: matrixSnapshot.mtxContentModifiedTime, + mtxCountModifiedTime: matrixSnapshot.mtxCountModifiedTime, + mtxDiffCount: matrixSnapshot.diff.length, + pMatrixModifiedTime: matrixSnapshot.pMatrixModifiedTime, + tMatrixModifiedTime: matrixSnapshot.tMatrixModifiedTime, + }, onPolled); + }; - var unpollAsync = function() { - if ( timer !== null ) { - clearTimeout(timer); + var poll = function() { timer = null; - } - }; + pollNow(); + }; - (function() { - var tabId = matrixSnapshot.tabId; - - // If no tab id yet, see if there is one specified in our URL - if ( tabId === undefined ) { - var matches = window.location.search.match(/(?:\?|&)tabId=([^&]+)/); - if ( matches !== null ) { - tabId = matches[1]; - // No need for logger button when embedded in logger - uDom('[data-extension-url="logger-ui.html"]').remove(); + var pollAsync = function() { + if ( timer !== null ) { + return; } - } + if ( document.defaultView === null ) { + return; + } + timer = vAPI.setTimeout(poll, 1414); + }; - var snapshotFetched = function(response) { - if ( typeof response === 'object' ) { - matrixSnapshot = preprocessMatrixSnapshot(response); + var unpollAsync = function() { + if ( timer !== null ) { + clearTimeout(timer); + timer = null; } - onMatrixSnapshotReady(response); - pollAsync(); }; - vAPI.messaging.send('popup.js', { - what: 'matrixSnapshot', - tabId: tabId - }, snapshotFetched); - })(); + (function() { + var tabId = matrixSnapshot.tabId; + + // If no tab id yet, see if there is one specified in our URL + if ( tabId === undefined ) { + var matches = window.location.search.match(/(?:\?|&)tabId=([^&]+)/); + if ( matches !== null ) { + tabId = matches[1]; + // No need for logger button when embedded in logger + uDom('[data-extension-url="logger-ui.html"]').remove(); + } + } - return { - pollNow: pollNow - }; -})(); + var snapshotFetched = function(response) { + if ( typeof response === 'object' ) { + matrixSnapshot = preprocessMatrixSnapshot(response); + } + onMatrixSnapshotReady(response); + pollAsync(); + }; + + vAPI.messaging.send('popup.js', { + what: 'matrixSnapshot', + tabId: tabId + }, snapshotFetched); + })(); + + return { + pollNow: pollNow + }; + })(); -/******************************************************************************/ + /******************************************************************************/ -// Below is UI stuff which is not key to make the menu, so this can -// be done without having to wait for a tab to be bound to the menu. + // Below is UI stuff which is not key to make the menu, so this can + // be done without having to wait for a tab to be bound to the menu. -// We reuse for all cells the one and only cell hotspots. -uDom('#whitelist').on('click', function() { + // We reuse for all cells the one and only cell hotspots. + uDom('#whitelist').on('click', function() { handleWhitelistFilter(uDom(this)); return false; }); -uDom('#blacklist').on('click', function() { + uDom('#blacklist').on('click', function() { handleBlacklistFilter(uDom(this)); return false; }); -uDom('#domainOnly').on('click', function() { + uDom('#domainOnly').on('click', function() { toggleCollapseState(uDom(this)); return false; }); -matrixCellHotspots = uDom('#cellHotspots').detach(); -uDom('body') - .on('mouseenter', '.matCell', mouseenterMatrixCellHandler) - .on('mouseleave', '.matCell', mouseleaveMatrixCellHandler); -uDom('#specificScope').on('click', selectSpecificScope); -uDom('#globalScope').on('click', selectGlobalScope); -uDom('[id^="mtxSwitch_"]').on('click', toggleMatrixSwitch); -uDom('#buttonPersist').on('click', persistMatrix); -uDom('#buttonRevertScope').on('click', revertMatrix); - -uDom('#buttonRevertAll').on('click', revertAll); -uDom('#buttonReload').on('click', buttonReloadHandler); -uDom('.extensionURL').on('click', gotoExtensionURL); - -uDom('body').on('click', '[data-dropdown-menu]', dropDownMenuShow); -uDom('body').on('click', '.dropdown-menu-capture', dropDownMenuHide); - -uDom('#matList').on('click', '.g4Meta', function(ev) { - matrixSnapshot.collapseBlacklistedDomains = - ev.target.classList.toggle('g4Collapsed'); - setUserSetting( - 'popupCollapseBlacklistedDomains', - matrixSnapshot.collapseBlacklistedDomains - ); -}); + matrixCellHotspots = uDom('#cellHotspots').detach(); + uDom('body') + .on('mouseenter', '.matCell', mouseenterMatrixCellHandler) + .on('mouseleave', '.matCell', mouseleaveMatrixCellHandler); + uDom('#specificScope').on('click', selectSpecificScope); + uDom('#globalScope').on('click', selectGlobalScope); + uDom('[id^="mtxSwitch_"]').on('click', toggleMatrixSwitch); + uDom('#buttonPersist').on('click', persistMatrix); + uDom('#buttonRevertScope').on('click', revertMatrix); + + uDom('#buttonRevertAll').on('click', revertAll); + uDom('#buttonReload').on('click', buttonReloadHandler); + uDom('.extensionURL').on('click', gotoExtensionURL); + + uDom('body').on('click', '[data-dropdown-menu]', dropDownMenuShow); + uDom('body').on('click', '.dropdown-menu-capture', dropDownMenuHide); + + uDom('#matList').on('click', '.g4Meta', function(ev) { + matrixSnapshot.collapseBlacklistedDomains = + ev.target.classList.toggle('g4Collapsed'); + setUserSetting( + 'popupCollapseBlacklistedDomains', + matrixSnapshot.collapseBlacklistedDomains + ); + }); -/******************************************************************************/ + /******************************************************************************/ })(); diff --git a/js/raw-settings.js b/js/raw-settings.js index 98032f6..6c3bb70 100644 --- a/js/raw-settings.js +++ b/js/raw-settings.js @@ -29,89 +29,89 @@ (function() { -/******************************************************************************/ + /******************************************************************************/ -var messaging = vAPI.messaging; -var cachedData = ''; -var rawSettingsInput = uDom.nodeFromId('rawSettings'); + var messaging = vAPI.messaging; + var cachedData = ''; + var rawSettingsInput = uDom.nodeFromId('rawSettings'); -/******************************************************************************/ + /******************************************************************************/ -var hashFromRawSettings = function(raw) { - return raw.trim().replace(/\s+/g, '|'); -}; + var hashFromRawSettings = function(raw) { + return raw.trim().replace(/\s+/g, '|'); + }; -/******************************************************************************/ + /******************************************************************************/ -// This is to give a visual hint that the content of user blacklist has changed. + // This is to give a visual hint that the content of user blacklist has changed. -var rawSettingsChanged = (function () { - var timer = null; + var rawSettingsChanged = (function () { + var timer = null; - var handler = function() { - timer = null; - var changed = - hashFromRawSettings(rawSettingsInput.value) !== cachedData; - uDom.nodeFromId('rawSettingsApply').disabled = !changed; - }; + var handler = function() { + timer = null; + var changed = + hashFromRawSettings(rawSettingsInput.value) !== cachedData; + uDom.nodeFromId('rawSettingsApply').disabled = !changed; + }; - return function() { - if ( timer !== null ) { - clearTimeout(timer); - } - timer = vAPI.setTimeout(handler, 100); - }; -})(); - -/******************************************************************************/ - -function renderRawSettings() { - var onRead = function(raw) { - cachedData = hashFromRawSettings(raw); - var pretty = [], - whitespaces = ' ', - lines = raw.split('\n'), - max = 0, - pos, - i, n = lines.length; - for ( i = 0; i < n; i++ ) { - pos = lines[i].indexOf(' '); - if ( pos > max ) { - max = pos; + return function() { + if ( timer !== null ) { + clearTimeout(timer); + } + timer = vAPI.setTimeout(handler, 100); + }; + })(); + + /******************************************************************************/ + + function renderRawSettings() { + var onRead = function(raw) { + cachedData = hashFromRawSettings(raw); + var pretty = [], + whitespaces = ' ', + lines = raw.split('\n'), + max = 0, + pos, + i, n = lines.length; + for ( i = 0; i < n; i++ ) { + pos = lines[i].indexOf(' '); + if ( pos > max ) { + max = pos; + } } - } - for ( i = 0; i < n; i++ ) { - pos = lines[i].indexOf(' '); - pretty.push(whitespaces.slice(0, max - pos) + lines[i]); - } - rawSettingsInput.value = pretty.join('\n') + '\n'; - rawSettingsChanged(); - rawSettingsInput.focus(); + for ( i = 0; i < n; i++ ) { + pos = lines[i].indexOf(' '); + pretty.push(whitespaces.slice(0, max - pos) + lines[i]); + } + rawSettingsInput.value = pretty.join('\n') + '\n'; + rawSettingsChanged(); + rawSettingsInput.focus(); + }; + messaging.send('dashboard', { what: 'readRawSettings' }, onRead); + } + + /******************************************************************************/ + + var applyChanges = function() { + messaging.send( + 'dashboard', + { + what: 'writeRawSettings', + content: rawSettingsInput.value + }, + renderRawSettings + ); }; - messaging.send('dashboard', { what: 'readRawSettings' }, onRead); -} - -/******************************************************************************/ - -var applyChanges = function() { - messaging.send( - 'dashboard', - { - what: 'writeRawSettings', - content: rawSettingsInput.value - }, - renderRawSettings - ); -}; -/******************************************************************************/ + /******************************************************************************/ -// Handle user interaction -uDom('#rawSettings').on('input', rawSettingsChanged); -uDom('#rawSettingsApply').on('click', applyChanges); + // Handle user interaction + uDom('#rawSettings').on('input', rawSettingsChanged); + uDom('#rawSettingsApply').on('click', applyChanges); -renderRawSettings(); + renderRawSettings(); -/******************************************************************************/ + /******************************************************************************/ })(); diff --git a/js/settings.js b/js/settings.js index f147e74..da8b6df 100644 --- a/js/settings.js +++ b/js/settings.js @@ -29,168 +29,168 @@ (function() { -/******************************************************************************/ - -var cachedSettings = {}; - -/******************************************************************************/ + /******************************************************************************/ -function changeUserSettings(name, value) { - vAPI.messaging.send('settings.js', { - what: 'userSettings', - name: name, - value: value - }); -} + var cachedSettings = {}; -/******************************************************************************/ + /******************************************************************************/ -function changeMatrixSwitch(name, state) { - vAPI.messaging.send('settings.js', { - what: 'setMatrixSwitch', - switchName: name, - state: state - }); -} + function changeUserSettings(name, value) { + vAPI.messaging.send('settings.js', { + what: 'userSettings', + name: name, + value: value + }); + } -/******************************************************************************/ + /******************************************************************************/ -function onChangeValueHandler(elem, setting, min, max) { - var oldVal = cachedSettings.userSettings[setting]; - var newVal = Math.round(parseFloat(elem.value)); - if ( typeof newVal !== 'number' ) { - newVal = oldVal; - } else { - newVal = Math.max(newVal, min); - newVal = Math.min(newVal, max); - } - elem.value = newVal; - if ( newVal !== oldVal ) { - changeUserSettings(setting, newVal); + function changeMatrixSwitch(name, state) { + vAPI.messaging.send('settings.js', { + what: 'setMatrixSwitch', + switchName: name, + state: state + }); } -} - -/******************************************************************************/ -function prepareToDie() { - onChangeValueHandler( - uDom.nodeFromId('deleteUnusedSessionCookiesAfter'), - 'deleteUnusedSessionCookiesAfter', - 15, 1440 - ); - onChangeValueHandler( - uDom.nodeFromId('clearBrowserCacheAfter'), - 'clearBrowserCacheAfter', - 15, 1440 - ); -} + /******************************************************************************/ + + function onChangeValueHandler(elem, setting, min, max) { + var oldVal = cachedSettings.userSettings[setting]; + var newVal = Math.round(parseFloat(elem.value)); + if ( typeof newVal !== 'number' ) { + newVal = oldVal; + } else { + newVal = Math.max(newVal, min); + newVal = Math.min(newVal, max); + } + elem.value = newVal; + if ( newVal !== oldVal ) { + changeUserSettings(setting, newVal); + } + } -/******************************************************************************/ + /******************************************************************************/ -function onInputChanged(ev) { - var target = ev.target; - - switch ( target.id ) { - case 'displayTextSize': - changeUserSettings('displayTextSize', target.value + 'px'); - break; - case 'clearBrowserCache': - case 'cloudStorageEnabled': - case 'collapseBlacklisted': - case 'collapseBlocked': - case 'colorBlindFriendly': - case 'deleteCookies': - case 'deleteLocalStorage': - case 'deleteUnusedSessionCookies': - case 'iconBadgeEnabled': - case 'processHyperlinkAuditing': - changeUserSettings(target.id, target.checked); - break; - case 'noMixedContent': - case 'noscriptTagsSpoofed': - case 'processReferer': - changeMatrixSwitch( - target.getAttribute('data-matrix-switch'), - target.checked + function prepareToDie() { + onChangeValueHandler( + uDom.nodeFromId('deleteUnusedSessionCookiesAfter'), + 'deleteUnusedSessionCookiesAfter', + 15, 1440 + ); + onChangeValueHandler( + uDom.nodeFromId('clearBrowserCacheAfter'), + 'clearBrowserCacheAfter', + 15, 1440 ); - break; - case 'deleteUnusedSessionCookiesAfter': - onChangeValueHandler(target, 'deleteUnusedSessionCookiesAfter', 15, 1440); - break; - case 'clearBrowserCacheAfter': - onChangeValueHandler(target, 'clearBrowserCacheAfter', 15, 1440); - break; - case 'popupScopeLevel': - changeUserSettings('popupScopeLevel', target.value); - break; - default: - break; } - switch ( target.id ) { - case 'collapseBlocked': - synchronizeWidgets(); - break; - default: - break; + /******************************************************************************/ + + function onInputChanged(ev) { + var target = ev.target; + + switch ( target.id ) { + case 'displayTextSize': + changeUserSettings('displayTextSize', target.value + 'px'); + break; + case 'clearBrowserCache': + case 'cloudStorageEnabled': + case 'collapseBlacklisted': + case 'collapseBlocked': + case 'colorBlindFriendly': + case 'deleteCookies': + case 'deleteLocalStorage': + case 'deleteUnusedSessionCookies': + case 'iconBadgeEnabled': + case 'processHyperlinkAuditing': + changeUserSettings(target.id, target.checked); + break; + case 'noMixedContent': + case 'noscriptTagsSpoofed': + case 'processReferer': + changeMatrixSwitch( + target.getAttribute('data-matrix-switch'), + target.checked + ); + break; + case 'deleteUnusedSessionCookiesAfter': + onChangeValueHandler(target, 'deleteUnusedSessionCookiesAfter', 15, 1440); + break; + case 'clearBrowserCacheAfter': + onChangeValueHandler(target, 'clearBrowserCacheAfter', 15, 1440); + break; + case 'popupScopeLevel': + changeUserSettings('popupScopeLevel', target.value); + break; + default: + break; + } + + switch ( target.id ) { + case 'collapseBlocked': + synchronizeWidgets(); + break; + default: + break; + } } -} -/******************************************************************************/ + /******************************************************************************/ -function synchronizeWidgets() { - var e1, e2; + function synchronizeWidgets() { + var e1, e2; - e1 = uDom.nodeFromId('collapseBlocked'); - e2 = uDom.nodeFromId('collapseBlacklisted'); - if ( e1.checked ) { - e2.setAttribute('disabled', ''); - } else { - e2.removeAttribute('disabled'); + e1 = uDom.nodeFromId('collapseBlocked'); + e2 = uDom.nodeFromId('collapseBlacklisted'); + if ( e1.checked ) { + e2.setAttribute('disabled', ''); + } else { + e2.removeAttribute('disabled'); + } } -} -/******************************************************************************/ + /******************************************************************************/ -vAPI.messaging.send( - 'settings.js', - { what: 'getUserSettings' }, - function onSettingsReceived(settings) { - // Cache copy - cachedSettings = settings; + vAPI.messaging.send( + 'settings.js', + { what: 'getUserSettings' }, + function onSettingsReceived(settings) { + // Cache copy + cachedSettings = settings; - var userSettings = settings.userSettings; - var matrixSwitches = settings.matrixSwitches; + var userSettings = settings.userSettings; + var matrixSwitches = settings.matrixSwitches; - uDom('[data-setting-bool]').forEach(function(elem){ - elem.prop('checked', userSettings[elem.prop('id')] === true); - }); + uDom('[data-setting-bool]').forEach(function(elem){ + elem.prop('checked', userSettings[elem.prop('id')] === true); + }); - uDom('[data-matrix-switch]').forEach(function(elem){ - var switchName = elem.attr('data-matrix-switch'); - if ( typeof switchName === 'string' && switchName !== '' ) { - elem.prop('checked', matrixSwitches[switchName] === true); - } - }); + uDom('[data-matrix-switch]').forEach(function(elem){ + var switchName = elem.attr('data-matrix-switch'); + if ( typeof switchName === 'string' && switchName !== '' ) { + elem.prop('checked', matrixSwitches[switchName] === true); + } + }); - uDom.nodeFromId('displayTextSize').value = - parseInt(userSettings.displayTextSize, 10) || 14; + uDom.nodeFromId('displayTextSize').value = + parseInt(userSettings.displayTextSize, 10) || 14; - uDom.nodeFromId('popupScopeLevel').value = userSettings.popupScopeLevel; - uDom.nodeFromId('deleteUnusedSessionCookiesAfter').value = - userSettings.deleteUnusedSessionCookiesAfter; - uDom.nodeFromId('clearBrowserCacheAfter').value = - userSettings.clearBrowserCacheAfter; + uDom.nodeFromId('popupScopeLevel').value = userSettings.popupScopeLevel; + uDom.nodeFromId('deleteUnusedSessionCookiesAfter').value = + userSettings.deleteUnusedSessionCookiesAfter; + uDom.nodeFromId('clearBrowserCacheAfter').value = + userSettings.clearBrowserCacheAfter; - synchronizeWidgets(); + synchronizeWidgets(); - document.addEventListener('change', onInputChanged); + document.addEventListener('change', onInputChanged); - // https://github.com/gorhill/httpswitchboard/issues/197 - uDom(window).on('beforeunload', prepareToDie); - } -); + // https://github.com/gorhill/httpswitchboard/issues/197 + uDom(window).on('beforeunload', prepareToDie); + } + ); -/******************************************************************************/ + /******************************************************************************/ })(); diff --git a/js/start.js b/js/start.js index 34ad071..31a89d8 100644 --- a/js/start.js +++ b/js/start.js @@ -31,78 +31,78 @@ (function() { -/******************************************************************************/ - -var ηm = ηMatrix; - -/******************************************************************************/ - -var processCallbackQueue = function(queue, callback) { - var processOne = function() { - var fn = queue.pop(); - if ( fn ) { - fn(processOne); - } else if ( typeof callback === 'function' ) { - callback(); - } + /******************************************************************************/ + + var ηm = ηMatrix; + + /******************************************************************************/ + + var processCallbackQueue = function(queue, callback) { + var processOne = function() { + var fn = queue.pop(); + if ( fn ) { + fn(processOne); + } else if ( typeof callback === 'function' ) { + callback(); + } + }; + processOne(); }; - processOne(); -}; -/******************************************************************************/ + /******************************************************************************/ -var onAllDone = function() { - ηm.webRequest.start(); + var onAllDone = function() { + ηm.webRequest.start(); - ηm.assets.addObserver(ηm.assetObserver.bind(ηm)); - ηm.scheduleAssetUpdater(ηm.userSettings.autoUpdate ? 7 * 60 * 1000 : 0); + ηm.assets.addObserver(ηm.assetObserver.bind(ηm)); + ηm.scheduleAssetUpdater(ηm.userSettings.autoUpdate ? 7 * 60 * 1000 : 0); - vAPI.cloud.start([ 'myRulesPane' ]); -}; + vAPI.cloud.start([ 'myRulesPane' ]); + }; -/******************************************************************************/ + /******************************************************************************/ -var onTabsReady = function(tabs) { - var tab; - var i = tabs.length; - // console.debug('start.js > binding %d tabs', i); - while ( i-- ) { - tab = tabs[i]; - ηm.tabContextManager.push(tab.id, tab.url, 'newURL'); - } + var onTabsReady = function(tabs) { + var tab; + var i = tabs.length; + // console.debug('start.js > binding %d tabs', i); + while ( i-- ) { + tab = tabs[i]; + ηm.tabContextManager.push(tab.id, tab.url, 'newURL'); + } - onAllDone(); -}; + onAllDone(); + }; -/******************************************************************************/ + /******************************************************************************/ -var onUserSettingsLoaded = function() { - ηm.loadHostsFiles(); -}; + var onUserSettingsLoaded = function() { + ηm.loadHostsFiles(); + }; -/******************************************************************************/ + /******************************************************************************/ -var onPSLReady = function() { - ηm.loadUserSettings(onUserSettingsLoaded); - ηm.loadRawSettings(); - ηm.loadMatrix(); + var onPSLReady = function() { + ηm.loadUserSettings(onUserSettingsLoaded); + ηm.loadRawSettings(); + ηm.loadMatrix(); - // rhill 2013-11-24: bind behind-the-scene virtual tab/url manually, since the - // normal way forbid binding behind the scene tab. - // https://github.com/gorhill/httpswitchboard/issues/67 - ηm.pageStores[vAPI.noTabId] = ηm.pageStoreFactory(ηm.tabContextManager.mustLookup(vAPI.noTabId)); - ηm.pageStores[vAPI.noTabId].title = vAPI.i18n('statsPageDetailedBehindTheScenePage'); + // rhill 2013-11-24: bind behind-the-scene virtual tab/url manually, since the + // normal way forbid binding behind the scene tab. + // https://github.com/gorhill/httpswitchboard/issues/67 + ηm.pageStores[vAPI.noTabId] = ηm.pageStoreFactory(ηm.tabContextManager.mustLookup(vAPI.noTabId)); + ηm.pageStores[vAPI.noTabId].title = vAPI.i18n('statsPageDetailedBehindTheScenePage'); - vAPI.tabs.getAll(onTabsReady); -}; + vAPI.tabs.getAll(onTabsReady); + }; -/******************************************************************************/ + /******************************************************************************/ -processCallbackQueue(ηm.onBeforeStartQueue, function() { - ηm.loadPublicSuffixList(onPSLReady); -}); + processCallbackQueue(ηm.onBeforeStartQueue, function() { + ηm.loadPublicSuffixList(onPSLReady); + }); -/******************************************************************************/ + /******************************************************************************/ })(); diff --git a/js/storage.js b/js/storage.js index 3302de0..a96caf3 100644 --- a/js/storage.js +++ b/js/storage.js @@ -27,13 +27,13 @@ /******************************************************************************/ -ηMatrix.getBytesInUse = function() { +ηMatrix.getBytesInUse = function () { var ηm = this; - var getBytesInUseHandler = function(bytesInUse) { + var getBytesInUseHandler = function (bytesInUse) { ηm.storageUsed = bytesInUse; }; // Not all WebExtension implementations support getBytesInUse(). - if ( typeof vAPI.storage.getBytesInUse === 'function' ) { + if (typeof vAPI.storage.getBytesInUse === 'function') { vAPI.storage.getBytesInUse(null, getBytesInUseHandler); } else { ηm.storageUsed = undefined; @@ -42,21 +42,21 @@ /******************************************************************************/ -ηMatrix.saveUserSettings = function() { +ηMatrix.saveUserSettings = function () { this.XAL.keyvalSetMany( this.userSettings, this.getBytesInUse.bind(this) ); }; -ηMatrix.loadUserSettings = function(callback) { +ηMatrix.loadUserSettings = function (callback) { var ηm = this; - if ( typeof callback !== 'function' ) { + if (typeof callback !== 'function') { callback = this.noopFunc; } - var settingsLoaded = function(store) { + var settingsLoaded = function (store) { // console.log('storage.js > loaded user settings'); ηm.userSettings = store; @@ -69,12 +69,14 @@ /******************************************************************************/ -ηMatrix.loadRawSettings = function() { +ηMatrix.loadRawSettings = function () { var ηm = this; - var onLoaded = function(bin) { - if ( !bin || bin.rawSettings instanceof Object === false ) { return; } - for ( var key of Object.keys(bin.rawSettings) ) { + var onLoaded = function (bin) { + if (!bin || bin.rawSettings instanceof Object === false) { + return; + } + for (var key of Object.keys(bin.rawSettings)) { if ( ηm.rawSettings.hasOwnProperty(key) === false || typeof bin.rawSettings[key] !== typeof ηm.rawSettings[key] @@ -89,15 +91,15 @@ vAPI.storage.get('rawSettings', onLoaded); }; -ηMatrix.saveRawSettings = function(rawSettings, callback) { +ηMatrix.saveRawSettings = function (rawSettings, callback) { var keys = Object.keys(rawSettings); - if ( keys.length === 0 ) { - if ( typeof callback === 'function' ) { + if (keys.length === 0) { + if (typeof callback === 'function') { callback(); } return; } - for ( var key of keys ) { + for (var key of keys) { if ( this.rawSettingsDefault.hasOwnProperty(key) && typeof rawSettings[key] === typeof this.rawSettingsDefault[key] @@ -105,57 +107,61 @@ this.rawSettings[key] = rawSettings[key]; } } - vAPI.storage.set({ rawSettings: this.rawSettings }, callback); + vAPI.storage.set({ + rawSettings: this.rawSettings + }, callback); this.rawSettingsWriteTime = Date.now(); }; -ηMatrix.rawSettingsFromString = function(raw) { +ηMatrix.rawSettingsFromString = function (raw) { var result = {}, lineIter = new this.LineIterator(raw), line, matches, name, value; - while ( lineIter.eot() === false ) { + while (lineIter.eot() === false) { line = lineIter.next().trim(); matches = /^(\S+)(\s+(.+))?$/.exec(line); - if ( matches === null ) { continue; } + if (matches === null) { + continue; + } name = matches[1]; - if ( this.rawSettingsDefault.hasOwnProperty(name) === false ) { + if (this.rawSettingsDefault.hasOwnProperty(name) === false) { continue; } value = (matches[2] || '').trim(); - switch ( typeof this.rawSettingsDefault[name] ) { - case 'boolean': - if ( value === 'true' ) { - value = true; - } else if ( value === 'false' ) { - value = false; - } else { - value = this.rawSettingsDefault[name]; - } - break; - case 'string': - if ( value === '' ) { - value = this.rawSettingsDefault[name]; - } - break; - case 'number': - value = parseInt(value, 10); - if ( isNaN(value) ) { - value = this.rawSettingsDefault[name]; - } - break; - default: - break; + switch (typeof this.rawSettingsDefault[name]) { + case 'boolean': + if (value === 'true') { + value = true; + } else if (value === 'false') { + value = false; + } else { + value = this.rawSettingsDefault[name]; + } + break; + case 'string': + if (value === '') { + value = this.rawSettingsDefault[name]; + } + break; + case 'number': + value = parseInt(value, 10); + if (isNaN(value)) { + value = this.rawSettingsDefault[name]; + } + break; + default: + break; } - if ( this.rawSettings[name] !== value ) { + if (this.rawSettings[name] !== value) { result[name] = value; } } this.saveRawSettings(result); }; -ηMatrix.stringFromRawSettings = function() { +ηMatrix.stringFromRawSettings = function () { var out = []; - for ( var key of Object.keys(this.rawSettings).sort() ) { + for (var key of Object.keys(this.rawSettings).sort()) { out.push(key + ' ' + this.rawSettings[key]); } return out.join('\n'); @@ -164,19 +170,19 @@ /******************************************************************************/ // save white/blacklist -ηMatrix.saveMatrix = function() { +ηMatrix.saveMatrix = function () { ηMatrix.XAL.keyvalSetOne('userMatrix', this.pMatrix.toString()); }; /******************************************************************************/ -ηMatrix.loadMatrix = function(callback) { - if ( typeof callback !== 'function' ) { +ηMatrix.loadMatrix = function (callback) { + if (typeof callback !== 'function') { callback = this.noopFunc; } var ηm = this; - var onLoaded = function(bin) { - if ( bin.hasOwnProperty('userMatrix') ) { + var onLoaded = function (bin) { + if (bin.hasOwnProperty('userMatrix')) { ηm.pMatrix.fromString(bin.userMatrix); ηm.tMatrix.assign(ηm.pMatrix); callback(); @@ -187,15 +193,17 @@ /******************************************************************************/ -ηMatrix.listKeysFromCustomHostsFiles = function(raw) { +ηMatrix.listKeysFromCustomHostsFiles = function (raw) { var out = new Set(), reIgnore = /^[!#]/, reValid = /^[a-z-]+:\/\/\S+/, lineIter = new this.LineIterator(raw), location; - while ( lineIter.eot() === false ) { + while (lineIter.eot() === false) { location = lineIter.next().trim(); - if ( reIgnore.test(location) || !reValid.test(location) ) { continue; } + if (reIgnore.test(location) || !reValid.test(location)) { + continue; + } out.add(location); } return this.setToArray(out); @@ -203,7 +211,7 @@ /******************************************************************************/ -ηMatrix.getAvailableHostsFiles = function(callback) { +ηMatrix.getAvailableHostsFiles = function (callback) { var ηm = this, availableHostsFiles = {}; @@ -211,7 +219,7 @@ var importedListKeys = this.listKeysFromCustomHostsFiles(ηm.userSettings.externalHostsFiles), i = importedListKeys.length, listKey, entry; - while ( i-- ) { + while (i--) { listKey = importedListKeys[i]; entry = { content: 'filters', @@ -225,31 +233,37 @@ } // selected lists - var onSelectedHostsFilesLoaded = function(bin) { + var onSelectedHostsFilesLoaded = function (bin) { // Now get user's selection of lists - for ( var assetKey in bin.liveHostsFiles ) { + for (var assetKey in bin.liveHostsFiles) { var availableEntry = availableHostsFiles[assetKey]; - if ( availableEntry === undefined ) { continue; } + if (availableEntry === undefined) { + continue; + } var liveEntry = bin.liveHostsFiles[assetKey]; availableEntry.off = liveEntry.off || false; - if ( liveEntry.entryCount !== undefined ) { + if (liveEntry.entryCount !== undefined) { availableEntry.entryCount = liveEntry.entryCount; } - if ( liveEntry.entryUsedCount !== undefined ) { + if (liveEntry.entryUsedCount !== undefined) { availableEntry.entryUsedCount = liveEntry.entryUsedCount; } // This may happen if the list name was pulled from the list content - if ( availableEntry.title === '' && liveEntry.title !== undefined ) { + if (availableEntry.title === '' && liveEntry.title !== undefined) { availableEntry.title = liveEntry.title; } } // Remove unreferenced imported filter lists. var dict = new Set(importedListKeys); - for ( assetKey in availableHostsFiles ) { + for (assetKey in availableHostsFiles) { var entry = availableHostsFiles[assetKey]; - if ( entry.submitter !== 'user' ) { continue; } - if ( dict.has(assetKey) ) { continue; } + if (entry.submitter !== 'user') { + continue; + } + if (dict.has(assetKey)) { + continue; + } delete availableHostsFiles[assetKey]; ηm.assets.unregisterAssetSource(assetKey); ηm.assets.remove(assetKey); @@ -259,18 +273,23 @@ }; // built-in lists - var onBuiltinHostsFilesLoaded = function(entries) { - for ( var assetKey in entries ) { - if ( entries.hasOwnProperty(assetKey) === false ) { continue; } + var onBuiltinHostsFilesLoaded = function (entries) { + for (var assetKey in entries) { + if (entries.hasOwnProperty(assetKey) === false) { + continue; + } entry = entries[assetKey]; - if ( entry.content !== 'filters' ) { continue; } + if (entry.content !== 'filters') { + continue; + } availableHostsFiles[assetKey] = objectAssign({}, entry); } // Now get user's selection of lists - vAPI.storage.get( - { 'liveHostsFiles': availableHostsFiles }, - onSelectedHostsFilesLoaded + vAPI.storage.get({ + 'liveHostsFiles': availableHostsFiles + }, + onSelectedHostsFilesLoaded ); }; @@ -279,31 +298,35 @@ /******************************************************************************/ -ηMatrix.loadHostsFiles = function(callback) { +ηMatrix.loadHostsFiles = function (callback) { var ηm = ηMatrix; var hostsFileLoadCount; - if ( typeof callback !== 'function' ) { + if (typeof callback !== 'function') { callback = this.noopFunc; } - var loadHostsFilesEnd = function() { + var loadHostsFilesEnd = function () { ηm.ubiquitousBlacklist.freeze(); - vAPI.storage.set({ 'liveHostsFiles': ηm.liveHostsFiles }); - vAPI.messaging.broadcast({ what: 'loadHostsFilesCompleted' }); + vAPI.storage.set({ + 'liveHostsFiles': ηm.liveHostsFiles + }); + vAPI.messaging.broadcast({ + what: 'loadHostsFilesCompleted' + }); ηm.getBytesInUse(); callback(); }; - var mergeHostsFile = function(details) { + var mergeHostsFile = function (details) { ηm.mergeHostsFile(details); hostsFileLoadCount -= 1; - if ( hostsFileLoadCount === 0 ) { + if (hostsFileLoadCount === 0) { loadHostsFilesEnd(); } }; - var loadHostsFilesStart = function(hostsFiles) { + var loadHostsFilesStart = function (hostsFiles) { ηm.liveHostsFiles = hostsFiles; ηm.ubiquitousBlacklist.reset(); var locations = Object.keys(hostsFiles); @@ -311,8 +334,8 @@ // Load all hosts file which are not disabled. var location; - while ( (location = locations.pop()) ) { - if ( hostsFiles[location].off ) { + while ((location = locations.pop())) { + if (hostsFiles[location].off) { hostsFileLoadCount -= 1; continue; } @@ -320,7 +343,7 @@ } // https://github.com/gorhill/uMatrix/issues/2 - if ( hostsFileLoadCount === 0 ) { + if (hostsFileLoadCount === 0) { loadHostsFilesEnd(); return; } @@ -331,7 +354,7 @@ /******************************************************************************/ -ηMatrix.mergeHostsFile = function(details) { +ηMatrix.mergeHostsFile = function (details) { var usedCount = this.ubiquitousBlacklist.count; var duplicateCount = this.ubiquitousBlacklist.duplicateCount; @@ -347,20 +370,21 @@ /******************************************************************************/ -ηMatrix.mergeHostsFileContent = function(rawText) { +ηMatrix.mergeHostsFileContent = function (rawText) { var rawEnd = rawText.length; var ubiquitousBlacklist = this.ubiquitousBlacklist; var reLocalhost = /(^|\s)(localhost\.localdomain|localhost|local|broadcasthost|0\.0\.0\.0|127\.0\.0\.1|::1|fe80::1%lo0)(?=\s|$)/g; var reAsciiSegment = /^[\x21-\x7e]+$/; var matches; - var lineBeg = 0, lineEnd; + var lineBeg = 0, + lineEnd; var line; - while ( lineBeg < rawEnd ) { + while (lineBeg < rawEnd) { lineEnd = rawText.indexOf('\n', lineBeg); - if ( lineEnd < 0 ) { + if (lineEnd < 0) { lineEnd = rawText.indexOf('\r', lineBeg); - if ( lineEnd < 0 ) { + if (lineEnd < 0) { lineEnd = rawEnd; } } @@ -382,19 +406,19 @@ // The filter is whatever sequence of printable ascii character without // whitespaces matches = reAsciiSegment.exec(line); - if ( !matches || matches.length === 0 ) { + if (!matches || matches.length === 0) { continue; } // Bypass anomalies // For example, when a filter contains whitespace characters, or // whatever else outside the range of printable ascii characters. - if ( matches[0] !== line ) { + if (matches[0] !== line) { continue; } line = matches[0]; - if ( line === '' ) { + if (line === '') { continue; } @@ -406,28 +430,28 @@ // `switches` contains the filter lists for which the switch must be revisited. -ηMatrix.selectHostsFiles = function(details, callback) { +ηMatrix.selectHostsFiles = function (details, callback) { var ηm = this, externalHostsFiles = this.userSettings.externalHostsFiles, i, n, assetKey; // Hosts file to select - if ( Array.isArray(details.toSelect) ) { - for ( assetKey in this.liveHostsFiles ) { - if ( this.liveHostsFiles.hasOwnProperty(assetKey) === false ) { + if (Array.isArray(details.toSelect)) { + for (assetKey in this.liveHostsFiles) { + if (this.liveHostsFiles.hasOwnProperty(assetKey) === false) { continue; } - if ( details.toSelect.indexOf(assetKey) !== -1 ) { + if (details.toSelect.indexOf(assetKey) !== -1) { this.liveHostsFiles[assetKey].off = false; - } else if ( details.merge !== true ) { + } else if (details.merge !== true) { this.liveHostsFiles[assetKey].off = true; } } } // Imported hosts files to remove - if ( Array.isArray(details.toRemove) ) { - var removeURLFromHaystack = function(haystack, needle) { + if (Array.isArray(details.toRemove)) { + var removeURLFromHaystack = function (haystack, needle) { return haystack.replace( new RegExp( '(^|\\n)' + @@ -436,7 +460,7 @@ '\n' ).trim(); }; - for ( i = 0, n = details.toRemove.length; i < n; i++ ) { + for (i = 0, n = details.toRemove.length; i < n; i++) { assetKey = details.toRemove[i]; delete this.liveHostsFiles[assetKey]; externalHostsFiles = removeURLFromHaystack(externalHostsFiles, assetKey); @@ -445,23 +469,30 @@ } // Hosts file to import - if ( typeof details.toImport === 'string' ) { + if (typeof details.toImport === 'string') { // https://github.com/gorhill/uBlock/issues/1181 // Try mapping the URL of an imported filter list to the assetKey of an // existing stock list. - var assetKeyFromURL = function(url) { + var assetKeyFromURL = function (url) { var needle = url.replace(/^https?:/, ''); - var assets = ηm.liveHostsFiles, asset; - for ( var assetKey in assets ) { + var assets = ηm.liveHostsFiles, + asset; + for (var assetKey in assets) { asset = assets[assetKey]; - if ( asset.content !== 'filters' ) { continue; } - if ( typeof asset.contentURL === 'string' ) { - if ( asset.contentURL.endsWith(needle) ) { return assetKey; } + if (asset.content !== 'filters') { continue; } - if ( Array.isArray(asset.contentURL) === false ) { continue; } - for ( i = 0, n = asset.contentURL.length; i < n; i++ ) { - if ( asset.contentURL[i].endsWith(needle) ) { + if (typeof asset.contentURL === 'string') { + if (asset.contentURL.endsWith(needle)) { + return assetKey; + } + continue; + } + if (Array.isArray(asset.contentURL) === false) { + continue; + } + for (i = 0, n = asset.contentURL.length; i < n; i++) { + if (asset.contentURL[i].endsWith(needle)) { return assetKey; } } @@ -473,28 +504,36 @@ iter = toImportSet.values(); for (;;) { var entry = iter.next(); - if ( entry.done ) { break; } - if ( importedSet.has(entry.value) ) { continue; } + if (entry.done) { + break; + } + if (importedSet.has(entry.value)) { + continue; + } assetKey = assetKeyFromURL(entry.value); - if ( assetKey === entry.value ) { + if (assetKey === entry.value) { importedSet.add(entry.value); } this.liveHostsFiles[assetKey] = { content: 'filters', - contentURL: [ assetKey ], + contentURL: [assetKey], title: assetKey }; } externalHostsFiles = this.setToArray(importedSet).sort().join('\n'); } - if ( externalHostsFiles !== this.userSettings.externalHostsFiles ) { + if (externalHostsFiles !== this.userSettings.externalHostsFiles) { this.userSettings.externalHostsFiles = externalHostsFiles; - vAPI.storage.set({ externalHostsFiles: externalHostsFiles }); + vAPI.storage.set({ + externalHostsFiles: externalHostsFiles + }); } - vAPI.storage.set({ 'liveHostsFiles': this.liveHostsFiles }); + vAPI.storage.set({ + 'liveHostsFiles': this.liveHostsFiles + }); - if ( typeof callback === 'function' ) { + if (typeof callback === 'function') { callback(); } }; @@ -504,19 +543,19 @@ // `switches` contains the preset blacklists for which the switch must be // revisited. -ηMatrix.reloadHostsFiles = function() { +ηMatrix.reloadHostsFiles = function () { this.loadHostsFiles(); }; /******************************************************************************/ -ηMatrix.loadPublicSuffixList = function(callback) { - if ( typeof callback !== 'function' ) { +ηMatrix.loadPublicSuffixList = function (callback) { + if (typeof callback !== 'function') { callback = this.noopFunc; } - var applyPublicSuffixList = function(details) { - if ( !details.error ) { + var applyPublicSuffixList = function (details) { + if (!details.error) { publicSuffixList.parse(details.content, punycode.toASCII); } callback(); @@ -527,37 +566,39 @@ /******************************************************************************/ -ηMatrix.scheduleAssetUpdater = (function() { +ηMatrix.scheduleAssetUpdater = (function () { var timer, next = 0; - return function(updateDelay) { - if ( timer ) { + return function (updateDelay) { + if (timer) { clearTimeout(timer); timer = undefined; } - if ( updateDelay === 0 ) { + if (updateDelay === 0) { next = 0; return; } var now = Date.now(); // Use the new schedule if and only if it is earlier than the previous // one. - if ( next !== 0 ) { + if (next !== 0) { updateDelay = Math.min(updateDelay, Math.max(next - now, 0)); } next = now + updateDelay; - timer = vAPI.setTimeout(function() { + timer = vAPI.setTimeout(function () { timer = undefined; next = 0; - ηMatrix.assets.updateStart({ delay: 120000 }); + ηMatrix.assets.updateStart({ + delay: 120000 + }); }, updateDelay); }; })(); /******************************************************************************/ -ηMatrix.assetObserver = function(topic, details) { +ηMatrix.assetObserver = function (topic, details) { // Do not update filter list if not in use. - if ( topic === 'before-asset-updated' ) { + if (topic === 'before-asset-updated') { if ( this.liveHostsFiles.hasOwnProperty(details.assetKey) === false || this.liveHostsFiles[details.assetKey].off === true @@ -567,7 +608,7 @@ return true; } - if ( topic === 'after-asset-updated' ) { + if (topic === 'after-asset-updated') { vAPI.messaging.broadcast({ what: 'assetUpdated', key: details.assetKey, @@ -577,7 +618,7 @@ } // Update failed. - if ( topic === 'asset-update-failed' ) { + if (topic === 'asset-update-failed') { vAPI.messaging.broadcast({ what: 'assetUpdated', key: details.assetKey, @@ -587,11 +628,11 @@ } // Reload all filter lists if needed. - if ( topic === 'after-assets-updated' ) { - if ( details.assetKeys.length !== 0 ) { + if (topic === 'after-assets-updated') { + if (details.assetKeys.length !== 0) { this.loadHostsFiles(); } - if ( this.userSettings.autoUpdate ) { + if (this.userSettings.autoUpdate) { this.scheduleAssetUpdater(25200000); } else { this.scheduleAssetUpdater(0); @@ -605,10 +646,10 @@ // New asset source became available, if it's a filter list, should we // auto-select it? - if ( topic === 'builtin-asset-source-added' ) { - if ( details.entry.content === 'filters' ) { - if ( details.entry.off !== true ) { - this.saveSelectedFilterLists([ details.assetKey ], true); + if (topic === 'builtin-asset-source-added') { + if (details.entry.content === 'filters') { + if (details.entry.off !== true) { + this.saveSelectedFilterLists([details.assetKey], true); } } return; @@ -26,71 +26,71 @@ (function() { -'use strict'; + 'use strict'; -/******************************************************************************/ + /******************************************************************************/ -var ηm = ηMatrix; + var ηm = ηMatrix; -// https://github.com/gorhill/httpswitchboard/issues/303 -// Some kind of trick going on here: -// Any scheme other than 'http' and 'https' is remapped into a fake -// URL which trick the rest of ηMatrix into being able to process an -// otherwise unmanageable scheme. ηMatrix needs web page to have a proper -// hostname to work properly, so just like the 'behind-the-scene' -// fake domain name, we map unknown schemes into a fake '{scheme}-scheme' -// hostname. This way, for a specific scheme you can create scope with -// rules which will apply only to that scheme. + // https://github.com/gorhill/httpswitchboard/issues/303 + // Some kind of trick going on here: + // Any scheme other than 'http' and 'https' is remapped into a fake + // URL which trick the rest of ηMatrix into being able to process an + // otherwise unmanageable scheme. ηMatrix needs web page to have a proper + // hostname to work properly, so just like the 'behind-the-scene' + // fake domain name, we map unknown schemes into a fake '{scheme}-scheme' + // hostname. This way, for a specific scheme you can create scope with + // rules which will apply only to that scheme. -/******************************************************************************/ -/******************************************************************************/ + /******************************************************************************/ + /******************************************************************************/ -ηm.normalizePageURL = function(tabId, pageURL) { - if ( vAPI.isBehindTheSceneTabId(tabId) ) { - return 'http://' + this.behindTheSceneScope + '/'; - } - - // https://github.com/gorhill/uMatrix/issues/992 - if (pageURL.startsWith('wyciwyg:')) { - // Matches strings like 'wyciwyg://101/' - let filter = /^wyciwyg:\/\/\d+\//.exec(pageURL); - if (filter) { - pageURL = pageURL.slice(filter[0].length); - } - } - - // If the URL is that of our "blocked page" document, return the URL of - // the blocked page. - if ( pageURL.lastIndexOf(vAPI.getURL('main-blocked.html'), 0) === 0 ) { - var matches = /main-blocked\.html\?details=([^&]+)/.exec(pageURL); - if ( matches && matches.length === 2 ) { - try { - var details = JSON.parse(atob(matches[1])); - pageURL = details.url; - } catch (e) { + ηm.normalizePageURL = function(tabId, pageURL) { + if ( vAPI.isBehindTheSceneTabId(tabId) ) { + return 'http://' + this.behindTheSceneScope + '/'; + } + + // https://github.com/gorhill/uMatrix/issues/992 + if (pageURL.startsWith('wyciwyg:')) { + // Matches strings like 'wyciwyg://101/' + let filter = /^wyciwyg:\/\/\d+\//.exec(pageURL); + if (filter) { + pageURL = pageURL.slice(filter[0].length); + } + } + + // If the URL is that of our "blocked page" document, return the URL of + // the blocked page. + if ( pageURL.lastIndexOf(vAPI.getURL('main-blocked.html'), 0) === 0 ) { + var matches = /main-blocked\.html\?details=([^&]+)/.exec(pageURL); + if ( matches && matches.length === 2 ) { + try { + var details = JSON.parse(atob(matches[1])); + pageURL = details.url; + } catch (e) { + } } } - } - var uri = this.URI.set(pageURL); - var scheme = uri.scheme; - if ( scheme === 'https' || scheme === 'http' ) { - return uri.normalizedURI(); - } + var uri = this.URI.set(pageURL); + var scheme = uri.scheme; + if ( scheme === 'https' || scheme === 'http' ) { + return uri.normalizedURI(); + } - var fakeHostname = scheme + '-scheme'; + var fakeHostname = scheme + '-scheme'; - if ( uri.hostname !== '' ) { - fakeHostname = uri.hostname + '.' + fakeHostname; - } else if ( scheme === 'about' ) { - fakeHostname = uri.path + '.' + fakeHostname; - } + if ( uri.hostname !== '' ) { + fakeHostname = uri.hostname + '.' + fakeHostname; + } else if ( scheme === 'about' ) { + fakeHostname = uri.path + '.' + fakeHostname; + } - return 'http://' + fakeHostname + '/'; -}; + return 'http://' + fakeHostname + '/'; + }; -/******************************************************************************/ -/****************************************************************************** + /******************************************************************************/ + /****************************************************************************** To keep track from which context *exactly* network requests are made. This is often tricky for various reasons, and the challenge is not specific to one @@ -142,579 +142,579 @@ master switch and dynamic filtering rules can be evaluated now properly even in the absence of a PageStore object, this was not the case before. Also, the TabContext object will try its best to find a good candidate root -document URL for when none exists. This takes care of +document URL for when none exists. This takes care of <https://github.com/chrisaljoudi/uBlock/issues/1001>. The TabContext manager is self-contained, and it takes care to properly housekeep itself. -*/ + */ -ηm.tabContextManager = (function() { - var tabContexts = Object.create(null); + ηm.tabContextManager = (function() { + var tabContexts = Object.create(null); - // https://github.com/chrisaljoudi/uBlock/issues/1001 - // This is to be used as last-resort fallback in case a tab is found to not - // be bound while network requests are fired for the tab. - var mostRecentRootDocURL = ''; - var mostRecentRootDocURLTimestamp = 0; + // https://github.com/chrisaljoudi/uBlock/issues/1001 + // This is to be used as last-resort fallback in case a tab is found to not + // be bound while network requests are fired for the tab. + var mostRecentRootDocURL = ''; + var mostRecentRootDocURLTimestamp = 0; + + var gcPeriod = 31 * 60 * 1000; // every 31 minutes + + // A pushed entry is removed from the stack unless it is committed with + // a set time. + var StackEntry = function(url, commit) { + this.url = url; + this.committed = commit; + this.tstamp = Date.now(); + }; - var gcPeriod = 31 * 60 * 1000; // every 31 minutes + var TabContext = function(tabId) { + this.tabId = tabId; + this.stack = []; + this.rawURL = + this.normalURL = + this.scheme = + this.rootHostname = + this.rootDomain = ''; + this.secure = false; + this.commitTimer = null; + this.gcTimer = null; - // A pushed entry is removed from the stack unless it is committed with - // a set time. - var StackEntry = function(url, commit) { - this.url = url; - this.committed = commit; - this.tstamp = Date.now(); - }; + tabContexts[tabId] = this; + }; - var TabContext = function(tabId) { - this.tabId = tabId; - this.stack = []; - this.rawURL = - this.normalURL = - this.scheme = - this.rootHostname = - this.rootDomain = ''; - this.secure = false; - this.commitTimer = null; - this.gcTimer = null; - - tabContexts[tabId] = this; - }; + TabContext.prototype.destroy = function() { + if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { + return; + } + if ( this.gcTimer !== null ) { + clearTimeout(this.gcTimer); + this.gcTimer = null; + } + delete tabContexts[this.tabId]; + }; - TabContext.prototype.destroy = function() { - if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { - return; - } - if ( this.gcTimer !== null ) { - clearTimeout(this.gcTimer); + TabContext.prototype.onTab = function(tab) { + if ( tab ) { + this.gcTimer = vAPI.setTimeout(this.onGC.bind(this), gcPeriod); + } else { + this.destroy(); + } + }; + + TabContext.prototype.onGC = function() { this.gcTimer = null; - } - delete tabContexts[this.tabId]; - }; + if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { + return; + } + vAPI.tabs.get(this.tabId, this.onTab.bind(this)); + }; + + // https://github.com/gorhill/uBlock/issues/248 + // Stack entries have to be committed to stick. Non-committed stack + // entries are removed after a set delay. + TabContext.prototype.onCommit = function() { + if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { + return; + } + this.commitTimer = null; + // Remove uncommitted entries at the top of the stack. + var i = this.stack.length; + while ( i-- ) { + if ( this.stack[i].committed ) { + break; + } + } + // https://github.com/gorhill/uBlock/issues/300 + // If no committed entry was found, fall back on the bottom-most one + // as being the committed one by default. + if ( i === -1 && this.stack.length !== 0 ) { + this.stack[0].committed = true; + i = 0; + } + i += 1; + if ( i < this.stack.length ) { + this.stack.length = i; + this.update(); + ηm.bindTabToPageStats(this.tabId, 'newURL'); + } + }; - TabContext.prototype.onTab = function(tab) { - if ( tab ) { + // This takes care of orphanized tab contexts. Can't be started for all + // contexts, as the behind-the-scene context is permanent -- so we do not + // want to flush it. + TabContext.prototype.autodestroy = function() { + if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { + return; + } this.gcTimer = vAPI.setTimeout(this.onGC.bind(this), gcPeriod); - } else { - this.destroy(); - } - }; + }; - TabContext.prototype.onGC = function() { - this.gcTimer = null; - if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { - return; - } - vAPI.tabs.get(this.tabId, this.onTab.bind(this)); - }; + // Update just force all properties to be updated to match the most recent + // root URL. + TabContext.prototype.update = function() { + if ( this.stack.length === 0 ) { + this.rawURL = this.normalURL = this.scheme = + this.rootHostname = this.rootDomain = ''; + this.secure = false; + return; + } + this.rawURL = this.stack[this.stack.length - 1].url; + this.normalURL = ηm.normalizePageURL(this.tabId, this.rawURL); + this.scheme = ηm.URI.schemeFromURI(this.rawURL); + this.rootHostname = ηm.URI.hostnameFromURI(this.normalURL); + this.rootDomain = ηm.URI.domainFromHostname(this.rootHostname) || this.rootHostname; + this.secure = ηm.URI.isSecureScheme(this.scheme); + }; - // https://github.com/gorhill/uBlock/issues/248 - // Stack entries have to be committed to stick. Non-committed stack - // entries are removed after a set delay. - TabContext.prototype.onCommit = function() { - if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { - return; - } - this.commitTimer = null; - // Remove uncommitted entries at the top of the stack. - var i = this.stack.length; - while ( i-- ) { - if ( this.stack[i].committed ) { - break; + // Called whenever a candidate root URL is spotted for the tab. + TabContext.prototype.push = function(url, context) { + if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; } + var committed = context !== undefined; + var count = this.stack.length; + var topEntry = this.stack[count - 1]; + if ( topEntry && topEntry.url === url ) { + if ( committed ) { + topEntry.committed = true; + } + return; + } + if ( this.commitTimer !== null ) { + clearTimeout(this.commitTimer); + } + if ( committed ) { + this.stack = [new StackEntry(url, true)]; + } else { + this.stack.push(new StackEntry(url)); + this.commitTimer = vAPI.setTimeout(this.onCommit.bind(this), 1000); } - } - // https://github.com/gorhill/uBlock/issues/300 - // If no committed entry was found, fall back on the bottom-most one - // as being the committed one by default. - if ( i === -1 && this.stack.length !== 0 ) { - this.stack[0].committed = true; - i = 0; - } - i += 1; - if ( i < this.stack.length ) { - this.stack.length = i; this.update(); - ηm.bindTabToPageStats(this.tabId, 'newURL'); - } - }; + ηm.bindTabToPageStats(this.tabId, context); + }; - // This takes care of orphanized tab contexts. Can't be started for all - // contexts, as the behind-the-scene context is permanent -- so we do not - // want to flush it. - TabContext.prototype.autodestroy = function() { - if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { - return; - } - this.gcTimer = vAPI.setTimeout(this.onGC.bind(this), gcPeriod); - }; + // These are to be used for the API of the tab context manager. - // Update just force all properties to be updated to match the most recent - // root URL. - TabContext.prototype.update = function() { - if ( this.stack.length === 0 ) { - this.rawURL = this.normalURL = this.scheme = - this.rootHostname = this.rootDomain = ''; - this.secure = false; - return; - } - this.rawURL = this.stack[this.stack.length - 1].url; - this.normalURL = ηm.normalizePageURL(this.tabId, this.rawURL); - this.scheme = ηm.URI.schemeFromURI(this.rawURL); - this.rootHostname = ηm.URI.hostnameFromURI(this.normalURL); - this.rootDomain = ηm.URI.domainFromHostname(this.rootHostname) || this.rootHostname; - this.secure = ηm.URI.isSecureScheme(this.scheme); - }; + var push = function(tabId, url, context) { + var entry = tabContexts[tabId]; + if ( entry === undefined ) { + entry = new TabContext(tabId); + entry.autodestroy(); + } + entry.push(url, context); + mostRecentRootDocURL = url; + mostRecentRootDocURLTimestamp = Date.now(); + return entry; + }; - // Called whenever a candidate root URL is spotted for the tab. - TabContext.prototype.push = function(url, context) { - if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; } - var committed = context !== undefined; - var count = this.stack.length; - var topEntry = this.stack[count - 1]; - if ( topEntry && topEntry.url === url ) { - if ( committed ) { - topEntry.committed = true; + // Find a tab context for a specific tab. If none is found, attempt to + // fix this. When all fail, the behind-the-scene context is returned. + var mustLookup = function(tabId, url) { + var entry; + if ( url !== undefined ) { + entry = push(tabId, url); + } else { + entry = tabContexts[tabId]; } - return; - } - if ( this.commitTimer !== null ) { - clearTimeout(this.commitTimer); - } - if ( committed ) { - this.stack = [new StackEntry(url, true)]; - } else { - this.stack.push(new StackEntry(url)); - this.commitTimer = vAPI.setTimeout(this.onCommit.bind(this), 1000); - } - this.update(); - ηm.bindTabToPageStats(this.tabId, context); - }; + if ( entry !== undefined ) { + return entry; + } + // https://github.com/chrisaljoudi/uBlock/issues/1025 + // Google Hangout popup opens without a root frame. So for now we will + // just discard that best-guess root frame if it is too far in the + // future, at which point it ceases to be a "best guess". + if ( mostRecentRootDocURL !== '' && mostRecentRootDocURLTimestamp + 500 < Date.now() ) { + mostRecentRootDocURL = ''; + } + // https://github.com/chrisaljoudi/uBlock/issues/1001 + // Not a behind-the-scene request, yet no page store found for the + // tab id: we will thus bind the last-seen root document to the + // unbound tab. It's a guess, but better than ending up filtering + // nothing at all. + if ( mostRecentRootDocURL !== '' ) { + return push(tabId, mostRecentRootDocURL); + } + // If all else fail at finding a page store, re-categorize the + // request as behind-the-scene. At least this ensures that ultimately + // the user can still inspect/filter those net requests which were + // about to fall through the cracks. + // Example: Chromium + case #12 at + // http://raymondhill.net/ublock/popup.html + return tabContexts[vAPI.noTabId]; + }; - // These are to be used for the API of the tab context manager. + var lookup = function(tabId) { + return tabContexts[tabId] || null; + }; - var push = function(tabId, url, context) { - var entry = tabContexts[tabId]; - if ( entry === undefined ) { - entry = new TabContext(tabId); - entry.autodestroy(); - } - entry.push(url, context); - mostRecentRootDocURL = url; - mostRecentRootDocURLTimestamp = Date.now(); - return entry; - }; + // Behind-the-scene tab context + (function() { + var entry = new TabContext(vAPI.noTabId); + entry.stack.push(new StackEntry('', true)); + entry.rawURL = ''; + entry.normalURL = ηm.normalizePageURL(entry.tabId); + entry.rootHostname = ηm.URI.hostnameFromURI(entry.normalURL); + entry.rootDomain = ηm.URI.domainFromHostname(entry.rootHostname) || entry.rootHostname; + })(); + + // https://github.com/gorhill/uMatrix/issues/513 + // Force a badge update here, it could happen that all the subsequent + // network requests are already in the page store, which would cause + // the badge to no be updated for these network requests. + + vAPI.tabs.onNavigation = function(details) { + var tabId = details.tabId; + if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } + push(tabId, details.url, 'newURL'); + ηm.updateBadgeAsync(tabId); + }; - // Find a tab context for a specific tab. If none is found, attempt to - // fix this. When all fail, the behind-the-scene context is returned. - var mustLookup = function(tabId, url) { - var entry; - if ( url !== undefined ) { - entry = push(tabId, url); - } else { - entry = tabContexts[tabId]; - } - if ( entry !== undefined ) { - return entry; - } - // https://github.com/chrisaljoudi/uBlock/issues/1025 - // Google Hangout popup opens without a root frame. So for now we will - // just discard that best-guess root frame if it is too far in the - // future, at which point it ceases to be a "best guess". - if ( mostRecentRootDocURL !== '' && mostRecentRootDocURLTimestamp + 500 < Date.now() ) { - mostRecentRootDocURL = ''; - } - // https://github.com/chrisaljoudi/uBlock/issues/1001 - // Not a behind-the-scene request, yet no page store found for the - // tab id: we will thus bind the last-seen root document to the - // unbound tab. It's a guess, but better than ending up filtering - // nothing at all. - if ( mostRecentRootDocURL !== '' ) { - return push(tabId, mostRecentRootDocURL); - } - // If all else fail at finding a page store, re-categorize the - // request as behind-the-scene. At least this ensures that ultimately - // the user can still inspect/filter those net requests which were - // about to fall through the cracks. - // Example: Chromium + case #12 at - // http://raymondhill.net/ublock/popup.html - return tabContexts[vAPI.noTabId]; - }; + // https://github.com/gorhill/uMatrix/issues/872 + // `changeInfo.url` may not always be available (Firefox). - var lookup = function(tabId) { - return tabContexts[tabId] || null; - }; + vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) { + if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } + if ( typeof tab.url !== 'string' || tab.url === '' ) { return; } + var url = changeInfo.url || tab.url; + if ( url ) { + push(tabId, url, 'updateURL'); + } + }; - // Behind-the-scene tab context - (function() { - var entry = new TabContext(vAPI.noTabId); - entry.stack.push(new StackEntry('', true)); - entry.rawURL = ''; - entry.normalURL = ηm.normalizePageURL(entry.tabId); - entry.rootHostname = ηm.URI.hostnameFromURI(entry.normalURL); - entry.rootDomain = ηm.URI.domainFromHostname(entry.rootHostname) || entry.rootHostname; + vAPI.tabs.onClosed = function(tabId) { + ηm.unbindTabFromPageStats(tabId); + var entry = tabContexts[tabId]; + if ( entry instanceof TabContext ) { + entry.destroy(); + } + }; + + return { + push: push, + lookup: lookup, + mustLookup: mustLookup + }; })(); - // https://github.com/gorhill/uMatrix/issues/513 - // Force a badge update here, it could happen that all the subsequent - // network requests are already in the page store, which would cause - // the badge to no be updated for these network requests. + vAPI.tabs.registerListeners(); - vAPI.tabs.onNavigation = function(details) { - var tabId = details.tabId; - if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } - push(tabId, details.url, 'newURL'); - ηm.updateBadgeAsync(tabId); - }; + /******************************************************************************/ + /******************************************************************************/ - // https://github.com/gorhill/uMatrix/issues/872 - // `changeInfo.url` may not always be available (Firefox). + // Create an entry for the tab if it doesn't exist - vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) { - if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } - if ( typeof tab.url !== 'string' || tab.url === '' ) { return; } - var url = changeInfo.url || tab.url; - if ( url ) { - push(tabId, url, 'updateURL'); + ηm.bindTabToPageStats = function(tabId, context) { + this.updateBadgeAsync(tabId); + + // Do not create a page store for URLs which are of no interests + // Example: dev console + var tabContext = this.tabContextManager.lookup(tabId); + if ( tabContext === null ) { + throw new Error('Unmanaged tab id: ' + tabId); } - }; - vAPI.tabs.onClosed = function(tabId) { - ηm.unbindTabFromPageStats(tabId); - var entry = tabContexts[tabId]; - if ( entry instanceof TabContext ) { - entry.destroy(); + // rhill 2013-11-24: Never ever rebind behind-the-scene + // virtual tab. + // https://github.com/gorhill/httpswitchboard/issues/67 + if ( vAPI.isBehindTheSceneTabId(tabId) ) { + return this.pageStores[tabId]; } - }; - return { - push: push, - lookup: lookup, - mustLookup: mustLookup - }; -})(); + var normalURL = tabContext.normalURL; + var pageStore = this.pageStores[tabId] || null; -vAPI.tabs.registerListeners(); + // The previous page URL, if any, associated with the tab + if ( pageStore !== null ) { + // No change, do not rebind + if ( pageStore.pageUrl === normalURL ) { + return pageStore; + } -/******************************************************************************/ -/******************************************************************************/ + // https://github.com/gorhill/uMatrix/issues/37 + // Just rebind whenever possible: the URL changed, but the document + // maybe is the same. + // Example: Google Maps, Github + // https://github.com/gorhill/uMatrix/issues/72 + // Need to double-check that the new scope is same as old scope + if ( context === 'updateURL' && pageStore.pageHostname === tabContext.rootHostname ) { + pageStore.rawURL = tabContext.rawURL; + pageStore.normalURL = normalURL; + this.updateTitle(tabId); + this.pageStoresToken = Date.now(); + return pageStore; + } -// Create an entry for the tab if it doesn't exist - -ηm.bindTabToPageStats = function(tabId, context) { - this.updateBadgeAsync(tabId); - - // Do not create a page store for URLs which are of no interests - // Example: dev console - var tabContext = this.tabContextManager.lookup(tabId); - if ( tabContext === null ) { - throw new Error('Unmanaged tab id: ' + tabId); - } - - // rhill 2013-11-24: Never ever rebind behind-the-scene - // virtual tab. - // https://github.com/gorhill/httpswitchboard/issues/67 - if ( vAPI.isBehindTheSceneTabId(tabId) ) { - return this.pageStores[tabId]; - } - - var normalURL = tabContext.normalURL; - var pageStore = this.pageStores[tabId] || null; - - // The previous page URL, if any, associated with the tab - if ( pageStore !== null ) { - // No change, do not rebind - if ( pageStore.pageUrl === normalURL ) { - return pageStore; + // We won't be reusing this page store. + this.unbindTabFromPageStats(tabId); } - // https://github.com/gorhill/uMatrix/issues/37 - // Just rebind whenever possible: the URL changed, but the document - // maybe is the same. - // Example: Google Maps, Github - // https://github.com/gorhill/uMatrix/issues/72 - // Need to double-check that the new scope is same as old scope - if ( context === 'updateURL' && pageStore.pageHostname === tabContext.rootHostname ) { - pageStore.rawURL = tabContext.rawURL; - pageStore.normalURL = normalURL; - this.updateTitle(tabId); - this.pageStoresToken = Date.now(); - return pageStore; + // Try to resurrect first. + pageStore = this.resurrectPageStore(tabId, normalURL); + if ( pageStore === null ) { + pageStore = this.pageStoreFactory(tabContext); } + this.pageStores[tabId] = pageStore; + this.updateTitle(tabId); + this.pageStoresToken = Date.now(); - // We won't be reusing this page store. - this.unbindTabFromPageStats(tabId); - } + // console.debug('tab.js > bindTabToPageStats(): dispatching traffic in tab id %d to page store "%s"', tabId, pageUrl); - // Try to resurrect first. - pageStore = this.resurrectPageStore(tabId, normalURL); - if ( pageStore === null ) { - pageStore = this.pageStoreFactory(tabContext); - } - this.pageStores[tabId] = pageStore; - this.updateTitle(tabId); - this.pageStoresToken = Date.now(); + return pageStore; + }; - // console.debug('tab.js > bindTabToPageStats(): dispatching traffic in tab id %d to page store "%s"', tabId, pageUrl); + /******************************************************************************/ - return pageStore; -}; + ηm.unbindTabFromPageStats = function(tabId) { + // Never unbind behind-the-scene page store. + if ( vAPI.isBehindTheSceneTabId(tabId) ) { + return; + } -/******************************************************************************/ + var pageStore = this.pageStores[tabId] || null; + if ( pageStore === null ) { + return; + } -ηm.unbindTabFromPageStats = function(tabId) { - // Never unbind behind-the-scene page store. - if ( vAPI.isBehindTheSceneTabId(tabId) ) { - return; - } + delete this.pageStores[tabId]; + this.pageStoresToken = Date.now(); - var pageStore = this.pageStores[tabId] || null; - if ( pageStore === null ) { - return; - } + if ( pageStore.incinerationTimer ) { + clearTimeout(pageStore.incinerationTimer); + pageStore.incinerationTimer = null; + } - delete this.pageStores[tabId]; - this.pageStoresToken = Date.now(); + if ( this.pageStoreCemetery.hasOwnProperty(tabId) === false ) { + this.pageStoreCemetery[tabId] = {}; + } + var pageStoreCrypt = this.pageStoreCemetery[tabId]; - if ( pageStore.incinerationTimer ) { - clearTimeout(pageStore.incinerationTimer); - pageStore.incinerationTimer = null; - } + var pageURL = pageStore.pageUrl; + pageStoreCrypt[pageURL] = pageStore; - if ( this.pageStoreCemetery.hasOwnProperty(tabId) === false ) { - this.pageStoreCemetery[tabId] = {}; - } - var pageStoreCrypt = this.pageStoreCemetery[tabId]; + pageStore.incinerationTimer = vAPI.setTimeout( + this.incineratePageStore.bind(this, tabId, pageURL), + 4 * 60 * 1000 + ); + }; - var pageURL = pageStore.pageUrl; - pageStoreCrypt[pageURL] = pageStore; + /******************************************************************************/ - pageStore.incinerationTimer = vAPI.setTimeout( - this.incineratePageStore.bind(this, tabId, pageURL), - 4 * 60 * 1000 - ); -}; + ηm.resurrectPageStore = function(tabId, pageURL) { + if ( this.pageStoreCemetery.hasOwnProperty(tabId) === false ) { + return null; + } + var pageStoreCrypt = this.pageStoreCemetery[tabId]; -/******************************************************************************/ + if ( pageStoreCrypt.hasOwnProperty(pageURL) === false ) { + return null; + } -ηm.resurrectPageStore = function(tabId, pageURL) { - if ( this.pageStoreCemetery.hasOwnProperty(tabId) === false ) { - return null; - } - var pageStoreCrypt = this.pageStoreCemetery[tabId]; + var pageStore = pageStoreCrypt[pageURL]; - if ( pageStoreCrypt.hasOwnProperty(pageURL) === false ) { - return null; - } + if ( pageStore.incinerationTimer !== null ) { + clearTimeout(pageStore.incinerationTimer); + pageStore.incinerationTimer = null; + } - var pageStore = pageStoreCrypt[pageURL]; + delete pageStoreCrypt[pageURL]; + if ( Object.keys(pageStoreCrypt).length === 0 ) { + delete this.pageStoreCemetery[tabId]; + } - if ( pageStore.incinerationTimer !== null ) { - clearTimeout(pageStore.incinerationTimer); - pageStore.incinerationTimer = null; - } + return pageStore; + }; - delete pageStoreCrypt[pageURL]; - if ( Object.keys(pageStoreCrypt).length === 0 ) { - delete this.pageStoreCemetery[tabId]; - } + /******************************************************************************/ - return pageStore; -}; + ηm.incineratePageStore = function(tabId, pageURL) { + if ( this.pageStoreCemetery.hasOwnProperty(tabId) === false ) { + return; + } + var pageStoreCrypt = this.pageStoreCemetery[tabId]; -/******************************************************************************/ + if ( pageStoreCrypt.hasOwnProperty(pageURL) === false ) { + return; + } -ηm.incineratePageStore = function(tabId, pageURL) { - if ( this.pageStoreCemetery.hasOwnProperty(tabId) === false ) { - return; - } - var pageStoreCrypt = this.pageStoreCemetery[tabId]; + var pageStore = pageStoreCrypt[pageURL]; + if ( pageStore.incinerationTimer !== null ) { + clearTimeout(pageStore.incinerationTimer); + pageStore.incinerationTimer = null; + } - if ( pageStoreCrypt.hasOwnProperty(pageURL) === false ) { - return; - } + delete pageStoreCrypt[pageURL]; + if ( Object.keys(pageStoreCrypt).length === 0 ) { + delete this.pageStoreCemetery[tabId]; + } - var pageStore = pageStoreCrypt[pageURL]; - if ( pageStore.incinerationTimer !== null ) { - clearTimeout(pageStore.incinerationTimer); - pageStore.incinerationTimer = null; - } + pageStore.dispose(); + }; - delete pageStoreCrypt[pageURL]; - if ( Object.keys(pageStoreCrypt).length === 0 ) { - delete this.pageStoreCemetery[tabId]; - } + /******************************************************************************/ - pageStore.dispose(); -}; + ηm.pageStoreFromTabId = function(tabId) { + return this.pageStores[tabId] || null; + }; -/******************************************************************************/ + // Never return null + ηm.mustPageStoreFromTabId = function(tabId) { + return this.pageStores[tabId] || this.pageStores[vAPI.noTabId]; + }; -ηm.pageStoreFromTabId = function(tabId) { - return this.pageStores[tabId] || null; -}; + /******************************************************************************/ -// Never return null -ηm.mustPageStoreFromTabId = function(tabId) { - return this.pageStores[tabId] || this.pageStores[vAPI.noTabId]; -}; + ηm.forceReload = function(tabId, bypassCache) { + vAPI.tabs.reload(tabId, bypassCache); + }; -/******************************************************************************/ + /******************************************************************************/ -ηm.forceReload = function(tabId, bypassCache) { - vAPI.tabs.reload(tabId, bypassCache); -}; + // Update badge -/******************************************************************************/ + // rhill 2013-11-09: well this sucks, I can't update icon/badge + // incrementally, as chromium overwrite the icon at some point without + // notifying me, and this causes internal cached state to be out of sync. -// Update badge + ηm.updateBadgeAsync = (function() { + var tabIdToTimer = Object.create(null); -// rhill 2013-11-09: well this sucks, I can't update icon/badge -// incrementally, as chromium overwrite the icon at some point without -// notifying me, and this causes internal cached state to be out of sync. + var updateBadge = function(tabId) { + delete tabIdToTimer[tabId]; -ηm.updateBadgeAsync = (function() { - var tabIdToTimer = Object.create(null); + var iconId = null; + var badgeStr = ''; - var updateBadge = function(tabId) { - delete tabIdToTimer[tabId]; + var pageStore = this.pageStoreFromTabId(tabId); + if ( pageStore !== null ) { + var total = pageStore.perLoadAllowedRequestCount + + pageStore.perLoadBlockedRequestCount; + if ( total ) { + var squareSize = 19; + var greenSize = squareSize * Math.sqrt(pageStore.perLoadAllowedRequestCount / total); + iconId = greenSize < squareSize/2 ? Math.ceil(greenSize) : Math.floor(greenSize); + } + if ( this.userSettings.iconBadgeEnabled && pageStore.perLoadBlockedRequestCount !== 0) { + badgeStr = this.formatCount(pageStore.perLoadBlockedRequestCount); + } + } - var iconId = null; - var badgeStr = ''; + vAPI.setIcon(tabId, iconId, badgeStr); + }; - var pageStore = this.pageStoreFromTabId(tabId); - if ( pageStore !== null ) { - var total = pageStore.perLoadAllowedRequestCount + - pageStore.perLoadBlockedRequestCount; - if ( total ) { - var squareSize = 19; - var greenSize = squareSize * Math.sqrt(pageStore.perLoadAllowedRequestCount / total); - iconId = greenSize < squareSize/2 ? Math.ceil(greenSize) : Math.floor(greenSize); + return function(tabId) { + if ( tabIdToTimer[tabId] ) { + return; } - if ( this.userSettings.iconBadgeEnabled && pageStore.perLoadBlockedRequestCount !== 0) { - badgeStr = this.formatCount(pageStore.perLoadBlockedRequestCount); + if ( vAPI.isBehindTheSceneTabId(tabId) ) { + return; } - } - - vAPI.setIcon(tabId, iconId, badgeStr); - }; - - return function(tabId) { - if ( tabIdToTimer[tabId] ) { - return; - } - if ( vAPI.isBehindTheSceneTabId(tabId) ) { - return; - } - tabIdToTimer[tabId] = vAPI.setTimeout(updateBadge.bind(this, tabId), 750); - }; -})(); - -/******************************************************************************/ + tabIdToTimer[tabId] = vAPI.setTimeout(updateBadge.bind(this, tabId), 750); + }; + })(); -ηm.updateTitle = (function() { - var tabIdToTimer = Object.create(null); - var tabIdToTryCount = Object.create(null); - var delay = 499; + /******************************************************************************/ - var tryNoMore = function(tabId) { - delete tabIdToTryCount[tabId]; - }; + ηm.updateTitle = (function() { + var tabIdToTimer = Object.create(null); + var tabIdToTryCount = Object.create(null); + var delay = 499; - var tryAgain = function(tabId) { - var count = tabIdToTryCount[tabId]; - if ( count === undefined ) { - return false; - } - if ( count === 1 ) { + var tryNoMore = function(tabId) { delete tabIdToTryCount[tabId]; - return false; - } - tabIdToTryCount[tabId] = count - 1; - tabIdToTimer[tabId] = vAPI.setTimeout(updateTitle.bind(ηm, tabId), delay); - return true; - }; + }; - var onTabReady = function(tabId, tab) { - if ( !tab ) { - return tryNoMore(tabId); - } - var pageStore = this.pageStoreFromTabId(tabId); - if ( pageStore === null ) { - return tryNoMore(tabId); - } - if ( !tab.title && tryAgain(tabId) ) { - return; - } - // https://github.com/gorhill/uMatrix/issues/225 - // Sometimes title changes while page is loading. - var settled = tab.title && tab.title === pageStore.title; - pageStore.title = tab.title || tab.url || ''; - this.pageStoresToken = Date.now(); - if ( settled || !tryAgain(tabId) ) { - tryNoMore(tabId); - } - }; + var tryAgain = function(tabId) { + var count = tabIdToTryCount[tabId]; + if ( count === undefined ) { + return false; + } + if ( count === 1 ) { + delete tabIdToTryCount[tabId]; + return false; + } + tabIdToTryCount[tabId] = count - 1; + tabIdToTimer[tabId] = vAPI.setTimeout(updateTitle.bind(ηm, tabId), delay); + return true; + }; - var updateTitle = function(tabId) { - delete tabIdToTimer[tabId]; - vAPI.tabs.get(tabId, onTabReady.bind(this, tabId)); - }; + var onTabReady = function(tabId, tab) { + if ( !tab ) { + return tryNoMore(tabId); + } + var pageStore = this.pageStoreFromTabId(tabId); + if ( pageStore === null ) { + return tryNoMore(tabId); + } + if ( !tab.title && tryAgain(tabId) ) { + return; + } + // https://github.com/gorhill/uMatrix/issues/225 + // Sometimes title changes while page is loading. + var settled = tab.title && tab.title === pageStore.title; + pageStore.title = tab.title || tab.url || ''; + this.pageStoresToken = Date.now(); + if ( settled || !tryAgain(tabId) ) { + tryNoMore(tabId); + } + }; - return function(tabId) { - if ( vAPI.isBehindTheSceneTabId(tabId) ) { - return; - } - if ( tabIdToTimer[tabId] ) { - clearTimeout(tabIdToTimer[tabId]); - } - tabIdToTimer[tabId] = vAPI.setTimeout(updateTitle.bind(this, tabId), delay); - tabIdToTryCount[tabId] = 5; - }; -})(); + var updateTitle = function(tabId) { + delete tabIdToTimer[tabId]; + vAPI.tabs.get(tabId, onTabReady.bind(this, tabId)); + }; -/******************************************************************************/ + return function(tabId) { + if ( vAPI.isBehindTheSceneTabId(tabId) ) { + return; + } + if ( tabIdToTimer[tabId] ) { + clearTimeout(tabIdToTimer[tabId]); + } + tabIdToTimer[tabId] = vAPI.setTimeout(updateTitle.bind(this, tabId), delay); + tabIdToTryCount[tabId] = 5; + }; + })(); -// Stale page store entries janitor -// https://github.com/chrisaljoudi/uBlock/issues/455 + /******************************************************************************/ -(function() { - var cleanupPeriod = 7 * 60 * 1000; - var cleanupSampleAt = 0; - var cleanupSampleSize = 11; - - var cleanup = function() { - var vapiTabs = vAPI.tabs; - var tabIds = Object.keys(ηm.pageStores).sort(); - var checkTab = function(tabId) { - vapiTabs.get(tabId, function(tab) { - if ( !tab ) { - ηm.unbindTabFromPageStats(tabId); + // Stale page store entries janitor + // https://github.com/chrisaljoudi/uBlock/issues/455 + + (function() { + var cleanupPeriod = 7 * 60 * 1000; + var cleanupSampleAt = 0; + var cleanupSampleSize = 11; + + var cleanup = function() { + var vapiTabs = vAPI.tabs; + var tabIds = Object.keys(ηm.pageStores).sort(); + var checkTab = function(tabId) { + vapiTabs.get(tabId, function(tab) { + if ( !tab ) { + ηm.unbindTabFromPageStats(tabId); + } + }); + }; + if ( cleanupSampleAt >= tabIds.length ) { + cleanupSampleAt = 0; + } + var tabId; + var n = Math.min(cleanupSampleAt + cleanupSampleSize, tabIds.length); + for ( var i = cleanupSampleAt; i < n; i++ ) { + tabId = tabIds[i]; + if ( vAPI.isBehindTheSceneTabId(tabId) ) { + continue; } - }); - }; - if ( cleanupSampleAt >= tabIds.length ) { - cleanupSampleAt = 0; - } - var tabId; - var n = Math.min(cleanupSampleAt + cleanupSampleSize, tabIds.length); - for ( var i = cleanupSampleAt; i < n; i++ ) { - tabId = tabIds[i]; - if ( vAPI.isBehindTheSceneTabId(tabId) ) { - continue; + checkTab(tabId); } - checkTab(tabId); - } - cleanupSampleAt = n; + cleanupSampleAt = n; - vAPI.setTimeout(cleanup, cleanupPeriod); - }; + vAPI.setTimeout(cleanup, cleanupPeriod); + }; - vAPI.setTimeout(cleanup, cleanupPeriod); -})(); + vAPI.setTimeout(cleanup, cleanupPeriod); + })(); -/******************************************************************************/ + /******************************************************************************/ })(); diff --git a/js/traffic.js b/js/traffic.js index 03846b1..6a6f700 100644 --- a/js/traffic.js +++ b/js/traffic.js @@ -29,417 +29,416 @@ ηMatrix.webRequest = (function() { -/******************************************************************************/ + /******************************************************************************/ + + // Intercept and filter web requests according to white and black lists. + + var onBeforeRootFrameRequestHandler = function(details) { + var ηm = ηMatrix; + var requestURL = details.url; + var requestHostname = ηm.URI.hostnameFromURI(requestURL); + var tabId = details.tabId; -// Intercept and filter web requests according to white and black lists. + ηm.tabContextManager.push(tabId, requestURL); -var onBeforeRootFrameRequestHandler = function(details) { - var ηm = ηMatrix; - var requestURL = details.url; - var requestHostname = ηm.URI.hostnameFromURI(requestURL); - var tabId = details.tabId; + var tabContext = ηm.tabContextManager.mustLookup(tabId); + var rootHostname = tabContext.rootHostname; - ηm.tabContextManager.push(tabId, requestURL); + // Disallow request as per matrix? + var block = ηm.mustBlock(rootHostname, requestHostname, 'doc'); - var tabContext = ηm.tabContextManager.mustLookup(tabId); - var rootHostname = tabContext.rootHostname; + var pageStore = ηm.pageStoreFromTabId(tabId); + pageStore.recordRequest('doc', requestURL, block); + ηm.logger.writeOne(tabId, 'net', rootHostname, requestURL, 'doc', block); - // Disallow request as per matrix? - var block = ηm.mustBlock(rootHostname, requestHostname, 'doc'); + // Not blocked + if ( !block ) { + // rhill 2013-11-07: Senseless to do this for behind-the-scene requests. + ηm.cookieHunter.recordPageCookies(pageStore); + return; + } - var pageStore = ηm.pageStoreFromTabId(tabId); - pageStore.recordRequest('doc', requestURL, block); - ηm.logger.writeOne(tabId, 'net', rootHostname, requestURL, 'doc', block); + // Blocked + var query = btoa(JSON.stringify({ + url: requestURL, + hn: requestHostname, + why: '?' + })); - // Not blocked - if ( !block ) { - // rhill 2013-11-07: Senseless to do this for behind-the-scene requests. - ηm.cookieHunter.recordPageCookies(pageStore); - return; - } + vAPI.tabs.replace(tabId, vAPI.getURL('main-blocked.html?details=') + query); - // Blocked - var query = btoa(JSON.stringify({ - url: requestURL, - hn: requestHostname, - why: '?' - })); + return { cancel: true }; + }; - vAPI.tabs.replace(tabId, vAPI.getURL('main-blocked.html?details=') + query); + /******************************************************************************/ - return { cancel: true }; -}; + // Intercept and filter web requests according to white and black lists. -/******************************************************************************/ + var onBeforeRequestHandler = function(details) { + var ηm = ηMatrix, + ηmuri = ηm.URI, + requestURL = details.url, + requestScheme = ηmuri.schemeFromURI(requestURL); -// Intercept and filter web requests according to white and black lists. - -var onBeforeRequestHandler = function(details) { - var ηm = ηMatrix, - ηmuri = ηm.URI, - requestURL = details.url, - requestScheme = ηmuri.schemeFromURI(requestURL); - - if ( ηmuri.isNetworkScheme(requestScheme) === false ) { return; } - - var requestType = requestTypeNormalizer[details.type] || 'other'; - - // https://github.com/gorhill/httpswitchboard/issues/303 - // Wherever the main doc comes from, create a receiver page URL: synthetize - // one if needed. - if ( requestType === 'doc' && details.parentFrameId === -1 ) { - return onBeforeRootFrameRequestHandler(details); - } - - // Re-classify orphan HTTP requests as behind-the-scene requests. There is - // not much else which can be done, because there are URLs - // which cannot be handled by ηMatrix, i.e. `opera://startpage`, - // as this would lead to complications with no obvious solution, like how - // to scope on unknown scheme? Etc. - // https://github.com/gorhill/httpswitchboard/issues/191 - // https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275 - var tabContext = ηm.tabContextManager.mustLookup(details.tabId), - tabId = tabContext.tabId, - rootHostname = tabContext.rootHostname, - specificity = 0; - - // Filter through matrix - var block = ηm.tMatrix.mustBlock( - rootHostname, - ηmuri.hostnameFromURI(requestURL), - requestType - ); - if ( block ) { - specificity = ηm.tMatrix.specificityRegister; - } - - // Record request. - // https://github.com/gorhill/httpswitchboard/issues/342 - // The way requests are handled now, it may happen at this point some - // processing has already been performed, and that a synthetic URL has - // been constructed for logging purpose. Use this synthetic URL if - // it is available. - var pageStore = ηm.mustPageStoreFromTabId(tabId); - - // Enforce strict secure connection? - if ( tabContext.secure && ηmuri.isSecureScheme(requestScheme) === false ) { - pageStore.hasMixedContent = true; - if ( block === false ) { - block = ηm.tMatrix.evaluateSwitchZ('https-strict', rootHostname); + if ( ηmuri.isNetworkScheme(requestScheme) === false ) { return; } + + var requestType = requestTypeNormalizer[details.type] || 'other'; + + // https://github.com/gorhill/httpswitchboard/issues/303 + // Wherever the main doc comes from, create a receiver page URL: synthetize + // one if needed. + if ( requestType === 'doc' && details.parentFrameId === -1 ) { + return onBeforeRootFrameRequestHandler(details); } - } - pageStore.recordRequest(requestType, requestURL, block); - ηm.logger.writeOne(tabId, 'net', rootHostname, requestURL, details.type, block); + // Re-classify orphan HTTP requests as behind-the-scene requests. There is + // not much else which can be done, because there are URLs + // which cannot be handled by ηMatrix, i.e. `opera://startpage`, + // as this would lead to complications with no obvious solution, like how + // to scope on unknown scheme? Etc. + // https://github.com/gorhill/httpswitchboard/issues/191 + // https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275 + var tabContext = ηm.tabContextManager.mustLookup(details.tabId), + tabId = tabContext.tabId, + rootHostname = tabContext.rootHostname, + specificity = 0; + + // Filter through matrix + var block = ηm.tMatrix.mustBlock( + rootHostname, + ηmuri.hostnameFromURI(requestURL), + requestType + ); + if ( block ) { + specificity = ηm.tMatrix.specificityRegister; + } - if ( block ) { - pageStore.cacheBlockedCollapsible(requestType, requestURL, specificity); - return { 'cancel': true }; - } -}; + // Record request. + // https://github.com/gorhill/httpswitchboard/issues/342 + // The way requests are handled now, it may happen at this point some + // processing has already been performed, and that a synthetic URL has + // been constructed for logging purpose. Use this synthetic URL if + // it is available. + var pageStore = ηm.mustPageStoreFromTabId(tabId); + + // Enforce strict secure connection? + if ( tabContext.secure && ηmuri.isSecureScheme(requestScheme) === false ) { + pageStore.hasMixedContent = true; + if ( block === false ) { + block = ηm.tMatrix.evaluateSwitchZ('https-strict', rootHostname); + } + } -/******************************************************************************/ + pageStore.recordRequest(requestType, requestURL, block); + ηm.logger.writeOne(tabId, 'net', rootHostname, requestURL, details.type, block); -// Sanitize outgoing headers as per user settings. - -var onBeforeSendHeadersHandler = function(details) { - var ηm = ηMatrix, - ηmuri = ηm.URI, - requestURL = details.url, - requestScheme = ηmuri.schemeFromURI(requestURL); - - // Ignore non-network schemes - if ( ηmuri.isNetworkScheme(requestScheme) === false ) { return; } - - // Re-classify orphan HTTP requests as behind-the-scene requests. There is - // not much else which can be done, because there are URLs - // which cannot be handled by HTTP Switchboard, i.e. `opera://startpage`, - // as this would lead to complications with no obvious solution, like how - // to scope on unknown scheme? Etc. - // https://github.com/gorhill/httpswitchboard/issues/191 - // https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275 - var tabId = details.tabId, - pageStore = ηm.mustPageStoreFromTabId(tabId), - requestType = requestTypeNormalizer[details.type] || 'other', - requestHeaders = details.requestHeaders, - headerIndex, headerValue; - - // https://github.com/gorhill/httpswitchboard/issues/342 - // Is this hyperlink auditing? - // If yes, create a synthetic URL for reporting hyperlink auditing - // in request log. This way the user is better informed of what went - // on. - - // https://html.spec.whatwg.org/multipage/links.html#hyperlink-auditing - // - // Target URL = the href of the link - // Doc URL = URL of the document containing the target URL - // Ping URLs = servers which will be told that user clicked target URL - // - // `Content-Type` = `text/ping` (always present) - // `Ping-To` = target URL (always present) - // `Ping-From` = doc URL - // `Referer` = doc URL - // request URL = URL which will receive the information - // - // With hyperlink-auditing, removing header(s) is pointless, the whole - // request must be cancelled. - - headerIndex = headerIndexFromName('ping-to', requestHeaders); - if ( headerIndex !== -1 ) { - headerValue = requestHeaders[headerIndex].value; - if ( headerValue !== '' ) { - var block = ηm.userSettings.processHyperlinkAuditing; - pageStore.recordRequest('other', requestURL + '{Ping-To:' + headerValue + '}', block); - ηm.logger.writeOne(tabId, 'net', '', requestURL, 'ping', block); - if ( block ) { - ηm.hyperlinkAuditingFoiledCounter += 1; - return { 'cancel': true }; + if ( block ) { + pageStore.cacheBlockedCollapsible(requestType, requestURL, specificity); + return { 'cancel': true }; + } + }; + + /******************************************************************************/ + + // Sanitize outgoing headers as per user settings. + + var onBeforeSendHeadersHandler = function(details) { + var ηm = ηMatrix, + ηmuri = ηm.URI, + requestURL = details.url, + requestScheme = ηmuri.schemeFromURI(requestURL); + + // Ignore non-network schemes + if ( ηmuri.isNetworkScheme(requestScheme) === false ) { return; } + + // Re-classify orphan HTTP requests as behind-the-scene requests. There is + // not much else which can be done, because there are URLs + // which cannot be handled by HTTP Switchboard, i.e. `opera://startpage`, + // as this would lead to complications with no obvious solution, like how + // to scope on unknown scheme? Etc. + // https://github.com/gorhill/httpswitchboard/issues/191 + // https://github.com/gorhill/httpswitchboard/issues/91#issuecomment-37180275 + var tabId = details.tabId, + pageStore = ηm.mustPageStoreFromTabId(tabId), + requestType = requestTypeNormalizer[details.type] || 'other', + requestHeaders = details.requestHeaders, + headerIndex, headerValue; + + // https://github.com/gorhill/httpswitchboard/issues/342 + // Is this hyperlink auditing? + // If yes, create a synthetic URL for reporting hyperlink auditing + // in request log. This way the user is better informed of what went + // on. + + // https://html.spec.whatwg.org/multipage/links.html#hyperlink-auditing + // + // Target URL = the href of the link + // Doc URL = URL of the document containing the target URL + // Ping URLs = servers which will be told that user clicked target URL + // + // `Content-Type` = `text/ping` (always present) + // `Ping-To` = target URL (always present) + // `Ping-From` = doc URL + // `Referer` = doc URL + // request URL = URL which will receive the information + // + // With hyperlink-auditing, removing header(s) is pointless, the whole + // request must be cancelled. + + headerIndex = headerIndexFromName('ping-to', requestHeaders); + if ( headerIndex !== -1 ) { + headerValue = requestHeaders[headerIndex].value; + if ( headerValue !== '' ) { + var block = ηm.userSettings.processHyperlinkAuditing; + pageStore.recordRequest('other', requestURL + '{Ping-To:' + headerValue + '}', block); + ηm.logger.writeOne(tabId, 'net', '', requestURL, 'ping', block); + if ( block ) { + ηm.hyperlinkAuditingFoiledCounter += 1; + return { 'cancel': true }; + } } } - } - - // If we reach this point, request is not blocked, so what is left to do - // is to sanitize headers. - - var rootHostname = pageStore.pageHostname, - requestHostname = ηmuri.hostnameFromURI(requestURL), - modified = false; - - // Process `Cookie` header. - - headerIndex = headerIndexFromName('cookie', requestHeaders); - if ( - headerIndex !== -1 && - ηm.mustBlock(rootHostname, requestHostname, 'cookie') - ) { - modified = true; - headerValue = requestHeaders[headerIndex].value; - requestHeaders.splice(headerIndex, 1); - ηm.cookieHeaderFoiledCounter++; - if ( requestType === 'doc' ) { - ηm.logger.writeOne(tabId, 'net', '', headerValue, 'COOKIE', true); + + // If we reach this point, request is not blocked, so what is left to do + // is to sanitize headers. + + var rootHostname = pageStore.pageHostname, + requestHostname = ηmuri.hostnameFromURI(requestURL), + modified = false; + + // Process `Cookie` header. + + headerIndex = headerIndexFromName('cookie', requestHeaders); + if ( + headerIndex !== -1 && + ηm.mustBlock(rootHostname, requestHostname, 'cookie') + ) { + modified = true; + headerValue = requestHeaders[headerIndex].value; + requestHeaders.splice(headerIndex, 1); + ηm.cookieHeaderFoiledCounter++; + if ( requestType === 'doc' ) { + ηm.logger.writeOne(tabId, 'net', '', headerValue, 'COOKIE', true); + } } - } - - // Process `Referer` header. - - // https://github.com/gorhill/httpswitchboard/issues/222#issuecomment-44828402 - - // https://github.com/gorhill/uMatrix/issues/320 - // http://tools.ietf.org/html/rfc6454#section-7.3 - // "The user agent MAY include an Origin header field in any HTTP - // "request. - // "The user agent MUST NOT include more than one Origin header field in - // "any HTTP request. - // "Whenever a user agent issues an HTTP request from a "privacy- - // "sensitive" context, the user agent MUST send the value "null" in the - // "Origin header field." - - // https://github.com/gorhill/uMatrix/issues/358 - // Do not spoof `Origin` header for the time being. - - // https://github.com/gorhill/uMatrix/issues/773 - // For non-GET requests, remove `Referer` header instead of spoofing it. - - headerIndex = headerIndexFromName('referer', requestHeaders); - if ( headerIndex !== -1 ) { - headerValue = requestHeaders[headerIndex].value; - if ( headerValue !== '' ) { - var toDomain = ηmuri.domainFromHostname(requestHostname); - if ( toDomain !== '' && toDomain !== ηmuri.domainFromURI(headerValue) ) { - pageStore.has3pReferrer = true; - if ( ηm.tMatrix.evaluateSwitchZ('referrer-spoof', rootHostname) ) { - modified = true; - var newValue; - if ( details.method === 'GET' ) { - newValue = requestHeaders[headerIndex].value = - requestScheme + '://' + requestHostname + '/'; - } else { - requestHeaders.splice(headerIndex, 1); - } - ηm.refererHeaderFoiledCounter++; - if ( requestType === 'doc' ) { - ηm.logger.writeOne(tabId, 'net', '', headerValue, 'REFERER', true); - if ( newValue !== undefined ) { - ηm.logger.writeOne(tabId, 'net', '', newValue, 'REFERER', false); + + // Process `Referer` header. + + // https://github.com/gorhill/httpswitchboard/issues/222#issuecomment-44828402 + + // https://github.com/gorhill/uMatrix/issues/320 + // http://tools.ietf.org/html/rfc6454#section-7.3 + // "The user agent MAY include an Origin header field in any HTTP + // "request. + // "The user agent MUST NOT include more than one Origin header field in + // "any HTTP request. + // "Whenever a user agent issues an HTTP request from a "privacy- + // "sensitive" context, the user agent MUST send the value "null" in the + // "Origin header field." + + // https://github.com/gorhill/uMatrix/issues/358 + // Do not spoof `Origin` header for the time being. + + // https://github.com/gorhill/uMatrix/issues/773 + // For non-GET requests, remove `Referer` header instead of spoofing it. + + headerIndex = headerIndexFromName('referer', requestHeaders); + if ( headerIndex !== -1 ) { + headerValue = requestHeaders[headerIndex].value; + if ( headerValue !== '' ) { + var toDomain = ηmuri.domainFromHostname(requestHostname); + if ( toDomain !== '' && toDomain !== ηmuri.domainFromURI(headerValue) ) { + pageStore.has3pReferrer = true; + if ( ηm.tMatrix.evaluateSwitchZ('referrer-spoof', rootHostname) ) { + modified = true; + var newValue; + if ( details.method === 'GET' ) { + newValue = requestHeaders[headerIndex].value = + requestScheme + '://' + requestHostname + '/'; + } else { + requestHeaders.splice(headerIndex, 1); + } + ηm.refererHeaderFoiledCounter++; + if ( requestType === 'doc' ) { + ηm.logger.writeOne(tabId, 'net', '', headerValue, 'REFERER', true); + if ( newValue !== undefined ) { + ηm.logger.writeOne(tabId, 'net', '', newValue, 'REFERER', false); + } } } } } } - } - if ( modified ) { - return { requestHeaders: requestHeaders }; - } -}; + if ( modified ) { + return { requestHeaders: requestHeaders }; + } + }; -/******************************************************************************/ + /******************************************************************************/ -// To prevent inline javascript from being executed. + // To prevent inline javascript from being executed. -// Prevent inline scripting using `Content-Security-Policy`: -// https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html + // Prevent inline scripting using `Content-Security-Policy`: + // https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html -// This fixes: -// https://github.com/gorhill/httpswitchboard/issues/35 + // This fixes: + // https://github.com/gorhill/httpswitchboard/issues/35 -var onHeadersReceived = function(details) { - // Ignore schemes other than 'http...' - var ηm = ηMatrix, - tabId = details.tabId, - requestURL = details.url, - requestType = requestTypeNormalizer[details.type] || 'other'; + var onHeadersReceived = function(details) { + // Ignore schemes other than 'http...' + var ηm = ηMatrix, + tabId = details.tabId, + requestURL = details.url, + requestType = requestTypeNormalizer[details.type] || 'other'; - // https://github.com/gorhill/uMatrix/issues/145 - // Check if the main_frame is a download - if ( requestType === 'doc' ) { - ηm.tabContextManager.push(tabId, requestURL); - } - - var tabContext = ηm.tabContextManager.lookup(tabId); - if ( tabContext === null ) { return; } - - var csp = [], - cspReport = [], - rootHostname = tabContext.rootHostname, - requestHostname = ηm.URI.hostnameFromURI(requestURL); - - // Inline script tags. - if ( ηm.mustAllow(rootHostname, requestHostname, 'script' ) !== true ) { - csp.push(ηm.cspNoInlineScript); - } - - // Inline style tags. - if ( ηm.mustAllow(rootHostname, requestHostname, 'css' ) !== true ) { - csp.push(ηm.cspNoInlineStyle); - } - - // https://bugzilla.mozilla.org/show_bug.cgi?id=1302667 - var cspNoWorker = ηm.cspNoWorker; - if ( cspNoWorker === undefined ) { - cspNoWorker = cspNoWorkerInit(); - } - - if ( ηm.tMatrix.evaluateSwitchZ('no-workers', rootHostname) ) { - csp.push(cspNoWorker); - } else if ( ηm.rawSettings.disableCSPReportInjection === false ) { - cspReport.push(cspNoWorker); - } - - var headers = details.responseHeaders, - cspDirectives, i; - - if ( csp.length !== 0 ) { - cspDirectives = csp.join(','); - i = headerIndexFromName('content-security-policy', headers); - if ( i !== -1 ) { - headers[i].value += ',' + cspDirectives; - } else { - headers.push({ - name: 'Content-Security-Policy', - value: cspDirectives - }); - } + // https://github.com/gorhill/uMatrix/issues/145 + // Check if the main_frame is a download if ( requestType === 'doc' ) { - ηm.logger.writeOne(tabId, 'net', '', cspDirectives, 'CSP', false); - } - } - - if ( cspReport.length !== 0 ) { - cspDirectives = cspReport.join(','); - i = headerIndexFromName('content-security-policy-report-only', headers); - if ( i !== -1 ) { - headers[i].value += ',' + cspDirectives; - } else { - headers.push({ - name: 'Content-Security-Policy-Report-Only', - value: cspDirectives - }); + ηm.tabContextManager.push(tabId, requestURL); } - } - - return { responseHeaders: headers }; -}; -/******************************************************************************/ - -var cspNoWorkerInit = function() { - if (ηMatrix.cspNoWorker === undefined) { - ηMatrix.cspNoWorker = "worker-src 'none'; " - +"frame-src data: blob: *; " - +"report-uri about:blank"; - } - - return ηMatrix.cspNoWorker; -}; + var tabContext = ηm.tabContextManager.lookup(tabId); + if ( tabContext === null ) { return; } -/******************************************************************************/ + var csp = [], + cspReport = [], + rootHostname = tabContext.rootHostname, + requestHostname = ηm.URI.hostnameFromURI(requestURL); -// Caller must ensure headerName is normalized to lower case. + // Inline script tags. + if ( ηm.mustAllow(rootHostname, requestHostname, 'script' ) !== true ) { + csp.push(ηm.cspNoInlineScript); + } -var headerIndexFromName = function(headerName, headers) { - var i = headers.length; - while ( i-- ) { - if ( headers[i].name.toLowerCase() === headerName ) { - return i; + // Inline style tags. + if ( ηm.mustAllow(rootHostname, requestHostname, 'css' ) !== true ) { + csp.push(ηm.cspNoInlineStyle); } - } - return -1; -}; -/******************************************************************************/ + // https://bugzilla.mozilla.org/show_bug.cgi?id=1302667 + var cspNoWorker = ηm.cspNoWorker; + if ( cspNoWorker === undefined ) { + cspNoWorker = cspNoWorkerInit(); + } -var requestTypeNormalizer = { - 'font' : 'css', - 'image' : 'image', - 'imageset' : 'image', - 'main_frame' : 'doc', - 'media' : 'media', - 'object' : 'media', - 'other' : 'other', - 'script' : 'script', - 'stylesheet' : 'css', - 'sub_frame' : 'frame', - 'websocket' : 'xhr', - 'xmlhttprequest': 'xhr' -}; + if ( ηm.tMatrix.evaluateSwitchZ('no-workers', rootHostname) ) { + csp.push(cspNoWorker); + } else if ( ηm.rawSettings.disableCSPReportInjection === false ) { + cspReport.push(cspNoWorker); + } -/******************************************************************************/ + var headers = details.responseHeaders, + cspDirectives, i; + + if ( csp.length !== 0 ) { + cspDirectives = csp.join(','); + i = headerIndexFromName('content-security-policy', headers); + if ( i !== -1 ) { + headers[i].value += ',' + cspDirectives; + } else { + headers.push({ + name: 'Content-Security-Policy', + value: cspDirectives + }); + } + if ( requestType === 'doc' ) { + ηm.logger.writeOne(tabId, 'net', '', cspDirectives, 'CSP', false); + } + } -vAPI.net.onBeforeRequest = { - extra: [ 'blocking' ], - callback: onBeforeRequestHandler -}; + if ( cspReport.length !== 0 ) { + cspDirectives = cspReport.join(','); + i = headerIndexFromName('content-security-policy-report-only', headers); + if ( i !== -1 ) { + headers[i].value += ',' + cspDirectives; + } else { + headers.push({ + name: 'Content-Security-Policy-Report-Only', + value: cspDirectives + }); + } + } -vAPI.net.onBeforeSendHeaders = { - extra: [ 'blocking', 'requestHeaders' ], - callback: onBeforeSendHeadersHandler -}; + return { responseHeaders: headers }; + }; -vAPI.net.onHeadersReceived = { - urls: [ 'http://*/*', 'https://*/*' ], - types: [ 'main_frame', 'sub_frame' ], - extra: [ 'blocking', 'responseHeaders' ], - callback: onHeadersReceived -}; + /******************************************************************************/ -/******************************************************************************/ + var cspNoWorkerInit = function() { + if (ηMatrix.cspNoWorker === undefined) { + ηMatrix.cspNoWorker = "worker-src 'none'; " + +"frame-src data: blob: *; " + +"report-uri about:blank"; + } -var start = function() { - vAPI.net.registerListeners(); -}; + return ηMatrix.cspNoWorker; + }; -/******************************************************************************/ + /******************************************************************************/ -return { - start: start -}; + // Caller must ensure headerName is normalized to lower case. -/******************************************************************************/ + var headerIndexFromName = function(headerName, headers) { + var i = headers.length; + while ( i-- ) { + if ( headers[i].name.toLowerCase() === headerName ) { + return i; + } + } + return -1; + }; + + /******************************************************************************/ + + var requestTypeNormalizer = { + 'font' : 'css', + 'image' : 'image', + 'imageset' : 'image', + 'main_frame' : 'doc', + 'media' : 'media', + 'object' : 'media', + 'other' : 'other', + 'script' : 'script', + 'stylesheet' : 'css', + 'sub_frame' : 'frame', + 'websocket' : 'xhr', + 'xmlhttprequest': 'xhr' + }; + + /******************************************************************************/ + + vAPI.net.onBeforeRequest = { + extra: [ 'blocking' ], + callback: onBeforeRequestHandler + }; + + vAPI.net.onBeforeSendHeaders = { + extra: [ 'blocking', 'requestHeaders' ], + callback: onBeforeSendHeadersHandler + }; + + vAPI.net.onHeadersReceived = { + urls: [ 'http://*/*', 'https://*/*' ], + types: [ 'main_frame', 'sub_frame' ], + extra: [ 'blocking', 'responseHeaders' ], + callback: onHeadersReceived + }; + + /******************************************************************************/ + + var start = function() { + vAPI.net.registerListeners(); + }; + + /******************************************************************************/ + + return { + start: start + }; + + /******************************************************************************/ })(); /******************************************************************************/ - @@ -36,695 +36,695 @@ var uDom = (function() { -/******************************************************************************/ + /******************************************************************************/ -var DOMList = function() { - this.nodes = []; -}; + var DOMList = function() { + this.nodes = []; + }; -/******************************************************************************/ + /******************************************************************************/ -Object.defineProperty( - DOMList.prototype, - 'length', - { - get: function() { - return this.nodes.length; + Object.defineProperty( + DOMList.prototype, + 'length', + { + get: function() { + return this.nodes.length; + } } - } -); + ); -/******************************************************************************/ + /******************************************************************************/ -var DOMListFactory = function(selector, context) { - var r = new DOMList(); - if ( typeof selector === 'string' ) { - selector = selector.trim(); - if ( selector !== '' ) { - return addSelectorToList(r, selector, context); - } - } - if ( selector instanceof Node ) { - return addNodeToList(r, selector); - } - if ( selector instanceof NodeList ) { - return addNodeListToList(r, selector); - } - if ( selector instanceof DOMList ) { - return addListToList(r, selector); - } - return r; -}; + var DOMListFactory = function(selector, context) { + var r = new DOMList(); + if ( typeof selector === 'string' ) { + selector = selector.trim(); + if ( selector !== '' ) { + return addSelectorToList(r, selector, context); + } + } + if ( selector instanceof Node ) { + return addNodeToList(r, selector); + } + if ( selector instanceof NodeList ) { + return addNodeListToList(r, selector); + } + if ( selector instanceof DOMList ) { + return addListToList(r, selector); + } + return r; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMListFactory.onLoad = function(callback) { - window.addEventListener('load', callback); -}; + DOMListFactory.onLoad = function(callback) { + window.addEventListener('load', callback); + }; -/******************************************************************************/ + /******************************************************************************/ -DOMListFactory.nodeFromId = function(id) { - return document.getElementById(id); -}; + DOMListFactory.nodeFromId = function(id) { + return document.getElementById(id); + }; -DOMListFactory.nodeFromSelector = function(selector) { - return document.querySelector(selector); -}; + DOMListFactory.nodeFromSelector = function(selector) { + return document.querySelector(selector); + }; -/******************************************************************************/ + /******************************************************************************/ -var addNodeToList = function(list, node) { - if ( node ) { - list.nodes.push(node); - } - return list; -}; + var addNodeToList = function(list, node) { + if ( node ) { + list.nodes.push(node); + } + return list; + }; -/******************************************************************************/ + /******************************************************************************/ -var addNodeListToList = function(list, nodelist) { - if ( nodelist ) { - var n = nodelist.length; - for ( var i = 0; i < n; i++ ) { - list.nodes.push(nodelist[i]); + var addNodeListToList = function(list, nodelist) { + if ( nodelist ) { + var n = nodelist.length; + for ( var i = 0; i < n; i++ ) { + list.nodes.push(nodelist[i]); + } } - } - return list; -}; + return list; + }; -/******************************************************************************/ + /******************************************************************************/ -var addListToList = function(list, other) { - list.nodes = list.nodes.concat(other.nodes); - return list; -}; + var addListToList = function(list, other) { + list.nodes = list.nodes.concat(other.nodes); + return list; + }; -/******************************************************************************/ + /******************************************************************************/ -var addSelectorToList = function(list, selector, context) { - var p = context || document; - var r = p.querySelectorAll(selector); - var n = r.length; - for ( var i = 0; i < n; i++ ) { - list.nodes.push(r[i]); - } - return list; -}; + var addSelectorToList = function(list, selector, context) { + var p = context || document; + var r = p.querySelectorAll(selector); + var n = r.length; + for ( var i = 0; i < n; i++ ) { + list.nodes.push(r[i]); + } + return list; + }; -/******************************************************************************/ + /******************************************************************************/ -var nodeInNodeList = function(node, nodeList) { - var i = nodeList.length; - while ( i-- ) { - if ( nodeList[i] === node ) { - return true; + var nodeInNodeList = function(node, nodeList) { + var i = nodeList.length; + while ( i-- ) { + if ( nodeList[i] === node ) { + return true; + } } - } - return false; -}; + return false; + }; -/******************************************************************************/ + /******************************************************************************/ -var doesMatchSelector = function(node, selector) { - if ( !node ) { - return false; - } - if ( node.nodeType !== 1 ) { - return false; - } - if ( selector === undefined ) { - return true; - } - var parentNode = node.parentNode; - if ( !parentNode || !parentNode.setAttribute ) { - return false; - } - var doesMatch = false; - parentNode.setAttribute('uDom-32kXc6xEZA7o73AMB8vLbLct1RZOkeoO', ''); - var grandpaNode = parentNode.parentNode || document; - var nl = grandpaNode.querySelectorAll('[uDom-32kXc6xEZA7o73AMB8vLbLct1RZOkeoO] > ' + selector); - var i = nl.length; - while ( doesMatch === false && i-- ) { - doesMatch = nl[i] === node; - } - parentNode.removeAttribute('uDom-32kXc6xEZA7o73AMB8vLbLct1RZOkeoO'); - return doesMatch; -}; + var doesMatchSelector = function(node, selector) { + if ( !node ) { + return false; + } + if ( node.nodeType !== 1 ) { + return false; + } + if ( selector === undefined ) { + return true; + } + var parentNode = node.parentNode; + if ( !parentNode || !parentNode.setAttribute ) { + return false; + } + var doesMatch = false; + parentNode.setAttribute('uDom-32kXc6xEZA7o73AMB8vLbLct1RZOkeoO', ''); + var grandpaNode = parentNode.parentNode || document; + var nl = grandpaNode.querySelectorAll('[uDom-32kXc6xEZA7o73AMB8vLbLct1RZOkeoO] > ' + selector); + var i = nl.length; + while ( doesMatch === false && i-- ) { + doesMatch = nl[i] === node; + } + parentNode.removeAttribute('uDom-32kXc6xEZA7o73AMB8vLbLct1RZOkeoO'); + return doesMatch; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.nodeAt = function(i) { - return this.nodes[i] || null; -}; + DOMList.prototype.nodeAt = function(i) { + return this.nodes[i] || null; + }; -DOMList.prototype.at = function(i) { - return addNodeToList(new DOMList(), this.nodes[i]); -}; + DOMList.prototype.at = function(i) { + return addNodeToList(new DOMList(), this.nodes[i]); + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.toArray = function() { - return this.nodes.slice(); -}; + DOMList.prototype.toArray = function() { + return this.nodes.slice(); + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.pop = function() { - return addNodeToList(new DOMList(), this.nodes.pop()); -}; + DOMList.prototype.pop = function() { + return addNodeToList(new DOMList(), this.nodes.pop()); + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.forEach = function(fn) { - var n = this.nodes.length; - for ( var i = 0; i < n; i++ ) { - fn(this.at(i), i); - } - return this; -}; + DOMList.prototype.forEach = function(fn) { + var n = this.nodes.length; + for ( var i = 0; i < n; i++ ) { + fn(this.at(i), i); + } + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.subset = function(i, l) { - var r = new DOMList(); - var n = l !== undefined ? l : this.nodes.length; - var j = Math.min(i + n, this.nodes.length); - if ( i < j ) { - r.nodes = this.nodes.slice(i, j); - } - return r; -}; + DOMList.prototype.subset = function(i, l) { + var r = new DOMList(); + var n = l !== undefined ? l : this.nodes.length; + var j = Math.min(i + n, this.nodes.length); + if ( i < j ) { + r.nodes = this.nodes.slice(i, j); + } + return r; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.first = function() { - return this.subset(0, 1); -}; + DOMList.prototype.first = function() { + return this.subset(0, 1); + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.next = function(selector) { - var r = new DOMList(); - var n = this.nodes.length; - var node; - for ( var i = 0; i < n; i++ ) { - node = this.nodes[i]; - while ( node.nextSibling !== null ) { - node = node.nextSibling; - if ( node.nodeType !== 1 ) { - continue; + DOMList.prototype.next = function(selector) { + var r = new DOMList(); + var n = this.nodes.length; + var node; + for ( var i = 0; i < n; i++ ) { + node = this.nodes[i]; + while ( node.nextSibling !== null ) { + node = node.nextSibling; + if ( node.nodeType !== 1 ) { + continue; + } + if ( doesMatchSelector(node, selector) === false ) { + continue; + } + addNodeToList(r, node); + break; } - if ( doesMatchSelector(node, selector) === false ) { - continue; + } + return r; + }; + + /******************************************************************************/ + + DOMList.prototype.parent = function() { + var r = new DOMList(); + if ( this.nodes.length ) { + addNodeToList(r, this.nodes[0].parentNode); + } + return r; + }; + + /******************************************************************************/ + + DOMList.prototype.filter = function(filter) { + var r = new DOMList(); + var filterFunc; + if ( typeof filter === 'string' ) { + filterFunc = function() { + return doesMatchSelector(this, filter); + }; + } else if ( typeof filter === 'function' ) { + filterFunc = filter; + } else { + filterFunc = function(){ + return true; + }; + } + var n = this.nodes.length; + var node; + for ( var i = 0; i < n; i++ ) { + node = this.nodes[i]; + if ( filterFunc.apply(node) ) { + addNodeToList(r, node); } - addNodeToList(r, node); - break; } - } - return r; -}; + return r; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.parent = function() { - var r = new DOMList(); - if ( this.nodes.length ) { - addNodeToList(r, this.nodes[0].parentNode); - } - return r; -}; + // TODO: Avoid possible duplicates -/******************************************************************************/ + DOMList.prototype.ancestors = function(selector) { + var r = new DOMList(); + var n = this.nodes.length; + var node; + for ( var i = 0; i < n; i++ ) { + node = this.nodes[i].parentNode; + while ( node ) { + if ( doesMatchSelector(node, selector) ) { + addNodeToList(r, node); + } + node = node.parentNode; + } + } + return r; + }; -DOMList.prototype.filter = function(filter) { - var r = new DOMList(); - var filterFunc; - if ( typeof filter === 'string' ) { - filterFunc = function() { - return doesMatchSelector(this, filter); - }; - } else if ( typeof filter === 'function' ) { - filterFunc = filter; - } else { - filterFunc = function(){ - return true; - }; - } - var n = this.nodes.length; - var node; - for ( var i = 0; i < n; i++ ) { - node = this.nodes[i]; - if ( filterFunc.apply(node) ) { - addNodeToList(r, node); - } - } - return r; -}; + /******************************************************************************/ -/******************************************************************************/ + DOMList.prototype.descendants = function(selector) { + var r = new DOMList(); + var n = this.nodes.length; + var nl; + for ( var i = 0; i < n; i++ ) { + nl = this.nodes[i].querySelectorAll(selector); + addNodeListToList(r, nl); + } + return r; + }; -// TODO: Avoid possible duplicates + /******************************************************************************/ -DOMList.prototype.ancestors = function(selector) { - var r = new DOMList(); - var n = this.nodes.length; - var node; - for ( var i = 0; i < n; i++ ) { - node = this.nodes[i].parentNode; - while ( node ) { - if ( doesMatchSelector(node, selector) ) { - addNodeToList(r, node); + DOMList.prototype.contents = function() { + var r = new DOMList(); + var cnodes, cn, ci; + var n = this.nodes.length; + for ( var i = 0; i < n; i++ ) { + cnodes = this.nodes[i].childNodes; + cn = cnodes.length; + for ( ci = 0; ci < cn; ci++ ) { + addNodeToList(r, cnodes.item(ci)); } - node = node.parentNode; } - } - return r; -}; + return r; + }; -/******************************************************************************/ + /******************************************************************************/ + + DOMList.prototype.remove = function() { + var cn, p; + var i = this.nodes.length; + while ( i-- ) { + cn = this.nodes[i]; + if ( (p = cn.parentNode) ) { + p.removeChild(cn); + } + } + return this; + }; -DOMList.prototype.descendants = function(selector) { - var r = new DOMList(); - var n = this.nodes.length; - var nl; - for ( var i = 0; i < n; i++ ) { - nl = this.nodes[i].querySelectorAll(selector); - addNodeListToList(r, nl); - } - return r; -}; + DOMList.prototype.detach = DOMList.prototype.remove; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.contents = function() { - var r = new DOMList(); - var cnodes, cn, ci; - var n = this.nodes.length; - for ( var i = 0; i < n; i++ ) { - cnodes = this.nodes[i].childNodes; - cn = cnodes.length; - for ( ci = 0; ci < cn; ci++ ) { - addNodeToList(r, cnodes.item(ci)); + DOMList.prototype.empty = function() { + var node; + var i = this.nodes.length; + while ( i-- ) { + node = this.nodes[i]; + while ( node.firstChild ) { + node.removeChild(node.firstChild); + } } - } - return r; -}; + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.remove = function() { - var cn, p; - var i = this.nodes.length; - while ( i-- ) { - cn = this.nodes[i]; - if ( (p = cn.parentNode) ) { - p.removeChild(cn); + DOMList.prototype.append = function(selector, context) { + var p = this.nodes[0]; + if ( p ) { + var c = DOMListFactory(selector, context); + var n = c.nodes.length; + for ( var i = 0; i < n; i++ ) { + p.appendChild(c.nodes[i]); + } } - } - return this; -}; + return this; + }; -DOMList.prototype.detach = DOMList.prototype.remove; + /******************************************************************************/ -/******************************************************************************/ + DOMList.prototype.prepend = function(selector, context) { + var p = this.nodes[0]; + if ( p ) { + var c = DOMListFactory(selector, context); + var i = c.nodes.length; + while ( i-- ) { + p.insertBefore(c.nodes[i], p.firstChild); + } + } + return this; + }; + + /******************************************************************************/ -DOMList.prototype.empty = function() { - var node; - var i = this.nodes.length; - while ( i-- ) { - node = this.nodes[i]; - while ( node.firstChild ) { - node.removeChild(node.firstChild); + DOMList.prototype.appendTo = function(selector, context) { + var p = selector instanceof DOMListFactory ? selector : DOMListFactory(selector, context); + var n = p.length; + for ( var i = 0; i < n; i++ ) { + p.nodes[0].appendChild(this.nodes[i]); } - } - return this; -}; + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.append = function(selector, context) { - var p = this.nodes[0]; - if ( p ) { + DOMList.prototype.insertAfter = function(selector, context) { + if ( this.nodes.length === 0 ) { + return this; + } + var p = this.nodes[0].parentNode; + if ( !p ) { + return this; + } var c = DOMListFactory(selector, context); var n = c.nodes.length; for ( var i = 0; i < n; i++ ) { p.appendChild(c.nodes[i]); } - } - return this; -}; + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.prepend = function(selector, context) { - var p = this.nodes[0]; - if ( p ) { - var c = DOMListFactory(selector, context); - var i = c.nodes.length; - while ( i-- ) { - p.insertBefore(c.nodes[i], p.firstChild); + DOMList.prototype.insertBefore = function(selector, context) { + if ( this.nodes.length === 0 ) { + return this; + } + var referenceNodes = DOMListFactory(selector, context); + if ( referenceNodes.nodes.length === 0 ) { + return this; + } + var referenceNode = referenceNodes.nodes[0]; + var parentNode = referenceNode.parentNode; + if ( !parentNode ) { + return this; } - } - return this; -}; + var n = this.nodes.length; + for ( var i = 0; i < n; i++ ) { + parentNode.insertBefore(this.nodes[i], referenceNode); + } + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.appendTo = function(selector, context) { - var p = selector instanceof DOMListFactory ? selector : DOMListFactory(selector, context); - var n = p.length; - for ( var i = 0; i < n; i++ ) { - p.nodes[0].appendChild(this.nodes[i]); - } - return this; -}; + DOMList.prototype.clone = function(notDeep) { + var r = new DOMList(); + var n = this.nodes.length; + for ( var i = 0; i < n; i++ ) { + addNodeToList(r, this.nodes[i].cloneNode(!notDeep)); + } + return r; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.insertAfter = function(selector, context) { - if ( this.nodes.length === 0 ) { - return this; - } - var p = this.nodes[0].parentNode; - if ( !p ) { - return this; - } - var c = DOMListFactory(selector, context); - var n = c.nodes.length; - for ( var i = 0; i < n; i++ ) { - p.appendChild(c.nodes[i]); - } - return this; -}; + DOMList.prototype.nthOfType = function() { + if ( this.nodes.length === 0 ) { + return 0; + } + var node = this.nodes[0]; + var tagName = node.tagName; + var i = 1; + while ( node.previousElementSibling !== null ) { + node = node.previousElementSibling; + if ( typeof node.tagName !== 'string' ) { + continue; + } + if ( node.tagName !== tagName ) { + continue; + } + i++; + } + return i; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.insertBefore = function(selector, context) { - if ( this.nodes.length === 0 ) { - return this; - } - var referenceNodes = DOMListFactory(selector, context); - if ( referenceNodes.nodes.length === 0 ) { - return this; - } - var referenceNode = referenceNodes.nodes[0]; - var parentNode = referenceNode.parentNode; - if ( !parentNode ) { + DOMList.prototype.attr = function(attr, value) { + var i = this.nodes.length; + if ( value === undefined && typeof attr !== 'object' ) { + return i ? this.nodes[0].getAttribute(attr) : undefined; + } + if ( typeof attr === 'object' ) { + var attrNames = Object.keys(attr); + var node, j, attrName; + while ( i-- ) { + node = this.nodes[i]; + j = attrNames.length; + while ( j-- ) { + attrName = attrNames[j]; + node.setAttribute(attrName, attr[attrName]); + } + } + } else { + while ( i-- ) { + this.nodes[i].setAttribute(attr, value); + } + } return this; - } - var n = this.nodes.length; - for ( var i = 0; i < n; i++ ) { - parentNode.insertBefore(this.nodes[i], referenceNode); - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.clone = function(notDeep) { - var r = new DOMList(); - var n = this.nodes.length; - for ( var i = 0; i < n; i++ ) { - addNodeToList(r, this.nodes[i].cloneNode(!notDeep)); - } - return r; -}; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.nthOfType = function() { - if ( this.nodes.length === 0 ) { - return 0; - } - var node = this.nodes[0]; - var tagName = node.tagName; - var i = 1; - while ( node.previousElementSibling !== null ) { - node = node.previousElementSibling; - if ( typeof node.tagName !== 'string' ) { - continue; - } - if ( node.tagName !== tagName ) { - continue; - } - i++; - } - return i; -}; + DOMList.prototype.prop = function(prop, value) { + var i = this.nodes.length; + if ( value === undefined ) { + return i !== 0 ? this.nodes[0][prop] : undefined; + } + while ( i-- ) { + this.nodes[i][prop] = value; + } + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.attr = function(attr, value) { - var i = this.nodes.length; - if ( value === undefined && typeof attr !== 'object' ) { - return i ? this.nodes[0].getAttribute(attr) : undefined; - } - if ( typeof attr === 'object' ) { - var attrNames = Object.keys(attr); - var node, j, attrName; - while ( i-- ) { - node = this.nodes[i]; - j = attrNames.length; - while ( j-- ) { - attrName = attrNames[j]; - node.setAttribute(attrName, attr[attrName]); + DOMList.prototype.css = function(prop, value) { + var i = this.nodes.length; + if ( value === undefined ) { + return i ? this.nodes[0].style[prop] : undefined; + } + if ( value !== '' ) { + while ( i-- ) { + this.nodes[i].style.setProperty(prop, value); } + return this; } - } else { while ( i-- ) { - this.nodes[i].setAttribute(attr, value); + this.nodes[i].style.removeProperty(prop); } - } - return this; -}; + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.prop = function(prop, value) { - var i = this.nodes.length; - if ( value === undefined ) { - return i !== 0 ? this.nodes[0][prop] : undefined; - } - while ( i-- ) { - this.nodes[i][prop] = value; - } - return this; -}; + DOMList.prototype.val = function(value) { + return this.prop('value', value); + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.css = function(prop, value) { - var i = this.nodes.length; - if ( value === undefined ) { - return i ? this.nodes[0].style[prop] : undefined; - } - if ( value !== '' ) { + DOMList.prototype.html = function(html) { + var i = this.nodes.length; + if ( html === undefined ) { + return i ? this.nodes[0].innerHTML : ''; + } while ( i-- ) { - this.nodes[i].style.setProperty(prop, value); + vAPI.insertHTML(this.nodes[i], html); } return this; - } - while ( i-- ) { - this.nodes[i].style.removeProperty(prop); - } - return this; -}; - -/******************************************************************************/ - -DOMList.prototype.val = function(value) { - return this.prop('value', value); -}; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.html = function(html) { - var i = this.nodes.length; - if ( html === undefined ) { - return i ? this.nodes[0].innerHTML : ''; - } - while ( i-- ) { - vAPI.insertHTML(this.nodes[i], html); - } - return this; -}; + DOMList.prototype.text = function(text) { + var i = this.nodes.length; + if ( text === undefined ) { + return i ? this.nodes[0].textContent : ''; + } + while ( i-- ) { + this.nodes[i].textContent = text; + } + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.text = function(text) { - var i = this.nodes.length; - if ( text === undefined ) { - return i ? this.nodes[0].textContent : ''; - } - while ( i-- ) { - this.nodes[i].textContent = text; - } - return this; -}; + var toggleClass = function(node, className, targetState) { + var tokenList = node.classList; + if ( tokenList instanceof DOMTokenList === false ) { + return; + } + var currentState = tokenList.contains(className); + var newState = targetState; + if ( newState === undefined ) { + newState = !currentState; + } + if ( newState === currentState ) { + return; + } + tokenList.toggle(className, newState); + }; -/******************************************************************************/ + /******************************************************************************/ -var toggleClass = function(node, className, targetState) { - var tokenList = node.classList; - if ( tokenList instanceof DOMTokenList === false ) { - return; - } - var currentState = tokenList.contains(className); - var newState = targetState; - if ( newState === undefined ) { - newState = !currentState; - } - if ( newState === currentState ) { - return; - } - tokenList.toggle(className, newState); -}; + DOMList.prototype.hasClass = function(className) { + if ( !this.nodes.length ) { + return false; + } + var tokenList = this.nodes[0].classList; + return tokenList instanceof DOMTokenList && + tokenList.contains(className); + }; + DOMList.prototype.hasClassName = DOMList.prototype.hasClass; -/******************************************************************************/ + DOMList.prototype.addClass = function(className) { + return this.toggleClass(className, true); + }; -DOMList.prototype.hasClass = function(className) { - if ( !this.nodes.length ) { - return false; - } - var tokenList = this.nodes[0].classList; - return tokenList instanceof DOMTokenList && - tokenList.contains(className); -}; -DOMList.prototype.hasClassName = DOMList.prototype.hasClass; - -DOMList.prototype.addClass = function(className) { - return this.toggleClass(className, true); -}; - -DOMList.prototype.removeClass = function(className) { - if ( className !== undefined ) { - return this.toggleClass(className, false); - } - var i = this.nodes.length; - while ( i-- ) { - this.nodes[i].className = ''; - } - return this; -}; + DOMList.prototype.removeClass = function(className) { + if ( className !== undefined ) { + return this.toggleClass(className, false); + } + var i = this.nodes.length; + while ( i-- ) { + this.nodes[i].className = ''; + } + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.toggleClass = function(className, targetState) { - if ( className.indexOf(' ') !== -1 ) { - return this.toggleClasses(className, targetState); - } - var i = this.nodes.length; - while ( i-- ) { - toggleClass(this.nodes[i], className, targetState); - } - return this; -}; + DOMList.prototype.toggleClass = function(className, targetState) { + if ( className.indexOf(' ') !== -1 ) { + return this.toggleClasses(className, targetState); + } + var i = this.nodes.length; + while ( i-- ) { + toggleClass(this.nodes[i], className, targetState); + } + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.toggleClasses = function(classNames, targetState) { - var tokens = classNames.split(/\s+/); - var i = this.nodes.length; - var node, j; - while ( i-- ) { - node = this.nodes[i]; - j = tokens.length; - while ( j-- ) { - toggleClass(node, tokens[j], targetState); + DOMList.prototype.toggleClasses = function(classNames, targetState) { + var tokens = classNames.split(/\s+/); + var i = this.nodes.length; + var node, j; + while ( i-- ) { + node = this.nodes[i]; + j = tokens.length; + while ( j-- ) { + toggleClass(node, tokens[j], targetState); + } } - } - return this; -}; + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -var listenerEntries = []; + var listenerEntries = []; -var ListenerEntry = function(target, type, capture, callback) { - this.target = target; - this.type = type; - this.capture = capture; - this.callback = callback; - target.addEventListener(type, callback, capture); -}; + var ListenerEntry = function(target, type, capture, callback) { + this.target = target; + this.type = type; + this.capture = capture; + this.callback = callback; + target.addEventListener(type, callback, capture); + }; -ListenerEntry.prototype.dispose = function() { - this.target.removeEventListener(this.type, this.callback, this.capture); - this.target = null; - this.callback = null; -}; + ListenerEntry.prototype.dispose = function() { + this.target.removeEventListener(this.type, this.callback, this.capture); + this.target = null; + this.callback = null; + }; -/******************************************************************************/ + /******************************************************************************/ -var makeEventHandler = function(selector, callback) { - return function(event) { - var dispatcher = event.currentTarget; - if ( !dispatcher || typeof dispatcher.querySelectorAll !== 'function' ) { - return; - } - var receiver = event.target; - if ( nodeInNodeList(receiver, dispatcher.querySelectorAll(selector)) ) { - callback.call(receiver, event); - } + var makeEventHandler = function(selector, callback) { + return function(event) { + var dispatcher = event.currentTarget; + if ( !dispatcher || typeof dispatcher.querySelectorAll !== 'function' ) { + return; + } + var receiver = event.target; + if ( nodeInNodeList(receiver, dispatcher.querySelectorAll(selector)) ) { + callback.call(receiver, event); + } + }; }; -}; -DOMList.prototype.on = function(etype, selector, callback) { - if ( typeof selector === 'function' ) { - callback = selector; - selector = undefined; - } else { - callback = makeEventHandler(selector, callback); - } + DOMList.prototype.on = function(etype, selector, callback) { + if ( typeof selector === 'function' ) { + callback = selector; + selector = undefined; + } else { + callback = makeEventHandler(selector, callback); + } - var i = this.nodes.length; - while ( i-- ) { - listenerEntries.push(new ListenerEntry(this.nodes[i], etype, selector !== undefined, callback)); - } - return this; -}; + var i = this.nodes.length; + while ( i-- ) { + listenerEntries.push(new ListenerEntry(this.nodes[i], etype, selector !== undefined, callback)); + } + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -// TODO: Won't work for delegated handlers. Need to figure -// what needs to be done. + // TODO: Won't work for delegated handlers. Need to figure + // what needs to be done. -DOMList.prototype.off = function(evtype, callback) { - var i = this.nodes.length; - while ( i-- ) { - this.nodes[i].removeEventListener(evtype, callback); - } - return this; -}; + DOMList.prototype.off = function(evtype, callback) { + var i = this.nodes.length; + while ( i-- ) { + this.nodes[i].removeEventListener(evtype, callback); + } + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -DOMList.prototype.trigger = function(etype) { - var ev = new CustomEvent(etype); - var i = this.nodes.length; - while ( i-- ) { - this.nodes[i].dispatchEvent(ev); - } - return this; -}; + DOMList.prototype.trigger = function(etype) { + var ev = new CustomEvent(etype); + var i = this.nodes.length; + while ( i-- ) { + this.nodes[i].dispatchEvent(ev); + } + return this; + }; -/******************************************************************************/ + /******************************************************************************/ -// Cleanup + // Cleanup -var onBeforeUnload = function() { - var entry; - while ( (entry = listenerEntries.pop()) ) { - entry.dispose(); - } - window.removeEventListener('beforeunload', onBeforeUnload); -}; + var onBeforeUnload = function() { + var entry; + while ( (entry = listenerEntries.pop()) ) { + entry.dispose(); + } + window.removeEventListener('beforeunload', onBeforeUnload); + }; -window.addEventListener('beforeunload', onBeforeUnload); + window.addEventListener('beforeunload', onBeforeUnload); -/******************************************************************************/ + /******************************************************************************/ -return DOMListFactory; + return DOMListFactory; })(); diff --git a/js/uritools.js b/js/uritools.js index 88d77de..d60af04 100644 --- a/js/uritools.js +++ b/js/uritools.js @@ -37,502 +37,501 @@ Naming convention from https://en.wikipedia.org/wiki/URI_scheme#Examples ηMatrix.URI = (function() { -/******************************************************************************/ + /******************************************************************************/ + + // Favorite regex tool: http://regex101.com/ + + // Ref: <http://tools.ietf.org/html/rfc3986#page-50> + // I removed redundant capture groups: capture less = peform faster. See + // <http://jsperf.com/old-uritools-vs-new-uritools> + // Performance improvements welcomed. + // jsperf: <http://jsperf.com/old-uritools-vs-new-uritools> + var reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/; + + // Derived + var reSchemeFromURI = /^[^:\/?#]+:/; + var reAuthorityFromURI = /^(?:[^:\/?#]+:)?(\/\/[^\/?#]+)/; + var reOriginFromURI = /^(?:[^:\/?#]+:)?(?:\/\/[^\/?#]+)/; + var reCommonHostnameFromURL = /^https?:\/\/([0-9a-z_][0-9a-z._-]*[0-9a-z])\//; + var rePathFromURI = /^(?:[^:\/?#]+:)?(?:\/\/[^\/?#]*)?([^?#]*)/; + var reMustNormalizeHostname = /[^0-9a-z._-]/; + + // These are to parse authority field, not parsed by above official regex + // IPv6 is seen as an exception: a non-compatible IPv6 is first tried, and + // if it fails, the IPv6 compatible regex istr used. This helps + // peformance by avoiding the use of a too complicated regex first. + + // https://github.com/gorhill/httpswitchboard/issues/211 + // "While a hostname may not contain other characters, such as the + // "underscore character (_), other DNS names may contain the underscore" + var reHostPortFromAuthority = /^(?:[^@]*@)?([^:]*)(:\d*)?$/; + var reIPv6PortFromAuthority = /^(?:[^@]*@)?(\[[0-9a-f:]*\])(:\d*)?$/i; + + var reHostFromNakedAuthority = /^[0-9a-z._-]+[0-9a-z]$/i; + var reHostFromAuthority = /^(?:[^@]*@)?([^:]+)(?::\d*)?$/; + var reIPv6FromAuthority = /^(?:[^@]*@)?(\[[0-9a-f:]+\])(?::\d*)?$/i; + + // Coarse (but fast) tests + var reValidHostname = /^([a-z\d]+(-*[a-z\d]+)*)(\.[a-z\d]+(-*[a-z\d])*)*$/; + var reIPAddressNaive = /^\d+\.\d+\.\d+\.\d+$|^\[[\da-zA-Z:]+\]$/; + + // Accurate tests + // Source.: http://stackoverflow.com/questions/5284147/validating-ipv4-addresses-with-regexp/5284410#5284410 + //var reIPv4 = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)(\.|$)){4}/; + + // Source: http://forums.intermapper.com/viewtopic.php?p=1096#1096 + //var reIPv6 = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/; + + /******************************************************************************/ + + var reset = function(o) { + o.scheme = ''; + o.hostname = ''; + o._ipv4 = undefined; + o._ipv6 = undefined; + o.port = ''; + o.path = ''; + o.query = ''; + o.fragment = ''; + return o; + }; + + var resetAuthority = function(o) { + o.hostname = ''; + o._ipv4 = undefined; + o._ipv6 = undefined; + o.port = ''; + return o; + }; + + /******************************************************************************/ + + // This will be exported + + var URI = { + scheme: '', + authority: '', + hostname: '', + _ipv4: undefined, + _ipv6: undefined, + port: '', + domain: undefined, + path: '', + query: '', + fragment: '', + schemeBit: (1 << 0), + userBit: (1 << 1), + passwordBit: (1 << 2), + hostnameBit: (1 << 3), + portBit: (1 << 4), + pathBit: (1 << 5), + queryBit: (1 << 6), + fragmentBit: (1 << 7), + allBits: (0xFFFF) + }; + + URI.authorityBit = (URI.userBit | URI.passwordBit | URI.hostnameBit | URI.portBit); + URI.normalizeBits = (URI.schemeBit | URI.hostnameBit | URI.pathBit | URI.queryBit); + + /******************************************************************************/ + + // See: https://en.wikipedia.org/wiki/URI_scheme#Examples + // URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + // + // foo://example.com:8042/over/there?name=ferret#nose + // \_/ \______________/\_________/ \_________/ \__/ + // | | | | | + // scheme authority path query fragment + // | _____________________|__ + // / \ / \ + // urn:example:animal:ferret:nose + + URI.set = function(uri) { + if ( uri === undefined ) { + return reset(URI); + } + var matches = reRFC3986.exec(uri); + if ( !matches ) { + return reset(URI); + } + this.scheme = matches[1] !== undefined ? matches[1].slice(0, -1) : ''; + this.authority = matches[2] !== undefined ? matches[2].slice(2).toLowerCase() : ''; + this.path = matches[3] !== undefined ? matches[3] : ''; + + // <http://tools.ietf.org/html/rfc3986#section-6.2.3> + // "In general, a URI that uses the generic syntax for authority + // "with an empty path should be normalized to a path of '/'." + if ( this.authority !== '' && this.path === '' ) { + this.path = '/'; + } + this.query = matches[4] !== undefined ? matches[4].slice(1) : ''; + this.fragment = matches[5] !== undefined ? matches[5].slice(1) : ''; + + // Assume very simple authority, i.e. just a hostname (highest likelihood + // case for ηMatrix) + if ( reHostFromNakedAuthority.test(this.authority) ) { + this.hostname = this.authority; + this.port = ''; + return this; + } + // Authority contains more than just a hostname + matches = reHostPortFromAuthority.exec(this.authority); + if ( !matches ) { + matches = reIPv6PortFromAuthority.exec(this.authority); + if ( !matches ) { + return resetAuthority(URI); + } + } + this.hostname = matches[1] !== undefined ? matches[1] : ''; + // http://en.wikipedia.org/wiki/FQDN + if ( this.hostname.slice(-1) === '.' ) { + this.hostname = this.hostname.slice(0, -1); + } + this.port = matches[2] !== undefined ? matches[2].slice(1) : ''; + return this; + }; + + /******************************************************************************/ + + // URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + // + // foo://example.com:8042/over/there?name=ferret#nose + // \_/ \______________/\_________/ \_________/ \__/ + // | | | | | + // scheme authority path query fragment + // | _____________________|__ + // / \ / \ + // urn:example:animal:ferret:nose + + URI.assemble = function(bits) { + if ( bits === undefined ) { + bits = this.allBits; + } + var s = []; + if ( this.scheme && (bits & this.schemeBit) ) { + s.push(this.scheme, ':'); + } + if ( this.hostname && (bits & this.hostnameBit) ) { + s.push('//', this.hostname); + } + if ( this.port && (bits & this.portBit) ) { + s.push(':', this.port); + } + if ( this.path && (bits & this.pathBit) ) { + s.push(this.path); + } + if ( this.query && (bits & this.queryBit) ) { + s.push('?', this.query); + } + if ( this.fragment && (bits & this.fragmentBit) ) { + s.push('#', this.fragment); + } + return s.join(''); + }; -// Favorite regex tool: http://regex101.com/ - -// Ref: <http://tools.ietf.org/html/rfc3986#page-50> -// I removed redundant capture groups: capture less = peform faster. See -// <http://jsperf.com/old-uritools-vs-new-uritools> -// Performance improvements welcomed. -// jsperf: <http://jsperf.com/old-uritools-vs-new-uritools> -var reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/; - -// Derived -var reSchemeFromURI = /^[^:\/?#]+:/; -var reAuthorityFromURI = /^(?:[^:\/?#]+:)?(\/\/[^\/?#]+)/; -var reOriginFromURI = /^(?:[^:\/?#]+:)?(?:\/\/[^\/?#]+)/; -var reCommonHostnameFromURL = /^https?:\/\/([0-9a-z_][0-9a-z._-]*[0-9a-z])\//; -var rePathFromURI = /^(?:[^:\/?#]+:)?(?:\/\/[^\/?#]*)?([^?#]*)/; -var reMustNormalizeHostname = /[^0-9a-z._-]/; - -// These are to parse authority field, not parsed by above official regex -// IPv6 is seen as an exception: a non-compatible IPv6 is first tried, and -// if it fails, the IPv6 compatible regex istr used. This helps -// peformance by avoiding the use of a too complicated regex first. - -// https://github.com/gorhill/httpswitchboard/issues/211 -// "While a hostname may not contain other characters, such as the -// "underscore character (_), other DNS names may contain the underscore" -var reHostPortFromAuthority = /^(?:[^@]*@)?([^:]*)(:\d*)?$/; -var reIPv6PortFromAuthority = /^(?:[^@]*@)?(\[[0-9a-f:]*\])(:\d*)?$/i; - -var reHostFromNakedAuthority = /^[0-9a-z._-]+[0-9a-z]$/i; -var reHostFromAuthority = /^(?:[^@]*@)?([^:]+)(?::\d*)?$/; -var reIPv6FromAuthority = /^(?:[^@]*@)?(\[[0-9a-f:]+\])(?::\d*)?$/i; - -// Coarse (but fast) tests -var reValidHostname = /^([a-z\d]+(-*[a-z\d]+)*)(\.[a-z\d]+(-*[a-z\d])*)*$/; -var reIPAddressNaive = /^\d+\.\d+\.\d+\.\d+$|^\[[\da-zA-Z:]+\]$/; - -// Accurate tests -// Source.: http://stackoverflow.com/questions/5284147/validating-ipv4-addresses-with-regexp/5284410#5284410 -//var reIPv4 = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)(\.|$)){4}/; - -// Source: http://forums.intermapper.com/viewtopic.php?p=1096#1096 -//var reIPv6 = /^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$/; + /******************************************************************************/ -/******************************************************************************/ + URI.originFromURI = function(uri) { + var matches = reOriginFromURI.exec(uri); + return matches !== null ? matches[0].toLowerCase() : ''; + }; -var reset = function(o) { - o.scheme = ''; - o.hostname = ''; - o._ipv4 = undefined; - o._ipv6 = undefined; - o.port = ''; - o.path = ''; - o.query = ''; - o.fragment = ''; - return o; -}; - -var resetAuthority = function(o) { - o.hostname = ''; - o._ipv4 = undefined; - o._ipv6 = undefined; - o.port = ''; - return o; -}; + /******************************************************************************/ -/******************************************************************************/ + URI.schemeFromURI = function(uri) { + var matches = reSchemeFromURI.exec(uri); + if ( matches === null ) { + return ''; + } + return matches[0].slice(0, -1).toLowerCase(); + }; -// This will be exported - -var URI = { - scheme: '', - authority: '', - hostname: '', - _ipv4: undefined, - _ipv6: undefined, - port: '', - domain: undefined, - path: '', - query: '', - fragment: '', - schemeBit: (1 << 0), - userBit: (1 << 1), - passwordBit: (1 << 2), - hostnameBit: (1 << 3), - portBit: (1 << 4), - pathBit: (1 << 5), - queryBit: (1 << 6), - fragmentBit: (1 << 7), - allBits: (0xFFFF) -}; - -URI.authorityBit = (URI.userBit | URI.passwordBit | URI.hostnameBit | URI.portBit); -URI.normalizeBits = (URI.schemeBit | URI.hostnameBit | URI.pathBit | URI.queryBit); + /******************************************************************************/ -/******************************************************************************/ + URI.isNetworkScheme = function(scheme) { + return this.reNetworkScheme.test(scheme); + }; -// See: https://en.wikipedia.org/wiki/URI_scheme#Examples -// URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] -// -// foo://example.com:8042/over/there?name=ferret#nose -// \_/ \______________/\_________/ \_________/ \__/ -// | | | | | -// scheme authority path query fragment -// | _____________________|__ -// / \ / \ -// urn:example:animal:ferret:nose - -URI.set = function(uri) { - if ( uri === undefined ) { - return reset(URI); - } - var matches = reRFC3986.exec(uri); - if ( !matches ) { - return reset(URI); - } - this.scheme = matches[1] !== undefined ? matches[1].slice(0, -1) : ''; - this.authority = matches[2] !== undefined ? matches[2].slice(2).toLowerCase() : ''; - this.path = matches[3] !== undefined ? matches[3] : ''; - - // <http://tools.ietf.org/html/rfc3986#section-6.2.3> - // "In general, a URI that uses the generic syntax for authority - // "with an empty path should be normalized to a path of '/'." - if ( this.authority !== '' && this.path === '' ) { - this.path = '/'; - } - this.query = matches[4] !== undefined ? matches[4].slice(1) : ''; - this.fragment = matches[5] !== undefined ? matches[5].slice(1) : ''; - - // Assume very simple authority, i.e. just a hostname (highest likelihood - // case for ηMatrix) - if ( reHostFromNakedAuthority.test(this.authority) ) { - this.hostname = this.authority; - this.port = ''; - return this; - } - // Authority contains more than just a hostname - matches = reHostPortFromAuthority.exec(this.authority); - if ( !matches ) { - matches = reIPv6PortFromAuthority.exec(this.authority); - if ( !matches ) { - return resetAuthority(URI); - } - } - this.hostname = matches[1] !== undefined ? matches[1] : ''; - // http://en.wikipedia.org/wiki/FQDN - if ( this.hostname.slice(-1) === '.' ) { - this.hostname = this.hostname.slice(0, -1); - } - this.port = matches[2] !== undefined ? matches[2].slice(1) : ''; - return this; -}; + URI.reNetworkScheme = /^(?:https?|wss?|ftps?)\b/; -/******************************************************************************/ + /******************************************************************************/ -// URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] -// -// foo://example.com:8042/over/there?name=ferret#nose -// \_/ \______________/\_________/ \_________/ \__/ -// | | | | | -// scheme authority path query fragment -// | _____________________|__ -// / \ / \ -// urn:example:animal:ferret:nose - -URI.assemble = function(bits) { - if ( bits === undefined ) { - bits = this.allBits; - } - var s = []; - if ( this.scheme && (bits & this.schemeBit) ) { - s.push(this.scheme, ':'); - } - if ( this.hostname && (bits & this.hostnameBit) ) { - s.push('//', this.hostname); - } - if ( this.port && (bits & this.portBit) ) { - s.push(':', this.port); - } - if ( this.path && (bits & this.pathBit) ) { - s.push(this.path); - } - if ( this.query && (bits & this.queryBit) ) { - s.push('?', this.query); - } - if ( this.fragment && (bits & this.fragmentBit) ) { - s.push('#', this.fragment); - } - return s.join(''); -}; + URI.isSecureScheme = function(scheme) { + return this.reSecureScheme.test(scheme); + }; -/******************************************************************************/ + URI.reSecureScheme = /^(?:https|wss|ftps)\b/; -URI.originFromURI = function(uri) { - var matches = reOriginFromURI.exec(uri); - return matches !== null ? matches[0].toLowerCase() : ''; -}; + /******************************************************************************/ -/******************************************************************************/ + URI.authorityFromURI = function(uri) { + var matches = reAuthorityFromURI.exec(uri); + if ( !matches ) { + return ''; + } + return matches[1].slice(2).toLowerCase(); + }; -URI.schemeFromURI = function(uri) { - var matches = reSchemeFromURI.exec(uri); - if ( matches === null ) { - return ''; - } - return matches[0].slice(0, -1).toLowerCase(); -}; + /******************************************************************************/ -/******************************************************************************/ + // The most used function, so it better be fast. -URI.isNetworkScheme = function(scheme) { - return this.reNetworkScheme.test(scheme); -}; + // https://github.com/gorhill/uBlock/issues/1559 + // See http://en.wikipedia.org/wiki/FQDN + // https://bugzilla.mozilla.org/show_bug.cgi?id=1360285 + // Revisit punycode dependency when above issue is fixed in Firefox. -URI.reNetworkScheme = /^(?:https?|wss?|ftps?)\b/; + URI.hostnameFromURI = function(uri) { + var matches = reCommonHostnameFromURL.exec(uri); + if ( matches !== null ) { return matches[1]; } + matches = reAuthorityFromURI.exec(uri); + if ( matches === null ) { return ''; } + var authority = matches[1].slice(2); + // Assume very simple authority (most common case for ηBlock) + if ( reHostFromNakedAuthority.test(authority) ) { + return authority.toLowerCase(); + } + matches = reHostFromAuthority.exec(authority); + if ( matches === null ) { + matches = reIPv6FromAuthority.exec(authority); + if ( matches === null ) { return ''; } + } + var hostname = matches[1]; + while ( hostname.endsWith('.') ) { + hostname = hostname.slice(0, -1); + } + if ( reMustNormalizeHostname.test(hostname) ) { + hostname = punycode.toASCII(hostname.toLowerCase()); + } + return hostname; + }; -/******************************************************************************/ + /******************************************************************************/ -URI.isSecureScheme = function(scheme) { - return this.reSecureScheme.test(scheme); -}; + URI.domainFromHostname = function(hostname) { + // Try to skip looking up the PSL database + var entry = domainCache.get(hostname); + if ( entry !== undefined ) { + entry.tstamp = Date.now(); + return entry.domain; + } + // Meh.. will have to search it + if ( reIPAddressNaive.test(hostname) === false ) { + return domainCacheAdd(hostname, psl.getDomain(hostname)); + } + return domainCacheAdd(hostname, hostname); + }; -URI.reSecureScheme = /^(?:https|wss|ftps)\b/; + URI.domain = function() { + return this.domainFromHostname(this.hostname); + }; -/******************************************************************************/ + // It is expected that there is higher-scoped `publicSuffixList` lingering + // somewhere. Cache it. See <https://github.com/gorhill/publicsuffixlist.js>. + var psl = publicSuffixList; -URI.authorityFromURI = function(uri) { - var matches = reAuthorityFromURI.exec(uri); - if ( !matches ) { - return ''; - } - return matches[1].slice(2).toLowerCase(); -}; + /******************************************************************************/ -/******************************************************************************/ + URI.pathFromURI = function(uri) { + var matches = rePathFromURI.exec(uri); + return matches !== null ? matches[1] : ''; + }; -// The most used function, so it better be fast. - -// https://github.com/gorhill/uBlock/issues/1559 -// See http://en.wikipedia.org/wiki/FQDN -// https://bugzilla.mozilla.org/show_bug.cgi?id=1360285 -// Revisit punycode dependency when above issue is fixed in Firefox. - -URI.hostnameFromURI = function(uri) { - var matches = reCommonHostnameFromURL.exec(uri); - if ( matches !== null ) { return matches[1]; } - matches = reAuthorityFromURI.exec(uri); - if ( matches === null ) { return ''; } - var authority = matches[1].slice(2); - // Assume very simple authority (most common case for ηBlock) - if ( reHostFromNakedAuthority.test(authority) ) { - return authority.toLowerCase(); - } - matches = reHostFromAuthority.exec(authority); - if ( matches === null ) { - matches = reIPv6FromAuthority.exec(authority); - if ( matches === null ) { return ''; } - } - var hostname = matches[1]; - while ( hostname.endsWith('.') ) { - hostname = hostname.slice(0, -1); - } - if ( reMustNormalizeHostname.test(hostname) ) { - hostname = punycode.toASCII(hostname.toLowerCase()); - } - return hostname; -}; + /******************************************************************************/ -/******************************************************************************/ + // Trying to alleviate the worries of looking up too often the domain name from + // a hostname. With a cache, uBlock benefits given that it deals with a + // specific set of hostnames within a narrow time span -- in other words, I + // believe probability of cache hit are high in uBlock. -URI.domainFromHostname = function(hostname) { - // Try to skip looking up the PSL database - var entry = domainCache.get(hostname); - if ( entry !== undefined ) { - entry.tstamp = Date.now(); - return entry.domain; - } - // Meh.. will have to search it - if ( reIPAddressNaive.test(hostname) === false ) { - return domainCacheAdd(hostname, psl.getDomain(hostname)); - } - return domainCacheAdd(hostname, hostname); -}; - -URI.domain = function() { - return this.domainFromHostname(this.hostname); -}; - -// It is expected that there is higher-scoped `publicSuffixList` lingering -// somewhere. Cache it. See <https://github.com/gorhill/publicsuffixlist.js>. -var psl = publicSuffixList; + var domainCache = new Map(); + var domainCacheCountLowWaterMark = 75; + var domainCacheCountHighWaterMark = 100; + var domainCacheEntryJunkyard = []; + var domainCacheEntryJunkyardMax = domainCacheCountHighWaterMark - domainCacheCountLowWaterMark; -/******************************************************************************/ + var DomainCacheEntry = function(domain) { + this.init(domain); + }; -URI.pathFromURI = function(uri) { - var matches = rePathFromURI.exec(uri); - return matches !== null ? matches[1] : ''; -}; - -/******************************************************************************/ + DomainCacheEntry.prototype.init = function(domain) { + this.domain = domain; + this.tstamp = Date.now(); + return this; + }; - // Trying to alleviate the worries of looking up too often the domain name from -// a hostname. With a cache, uBlock benefits given that it deals with a -// specific set of hostnames within a narrow time span -- in other words, I -// believe probability of cache hit are high in uBlock. - -var domainCache = new Map(); -var domainCacheCountLowWaterMark = 75; -var domainCacheCountHighWaterMark = 100; -var domainCacheEntryJunkyard = []; -var domainCacheEntryJunkyardMax = domainCacheCountHighWaterMark - domainCacheCountLowWaterMark; - -var DomainCacheEntry = function(domain) { - this.init(domain); -}; - -DomainCacheEntry.prototype.init = function(domain) { - this.domain = domain; - this.tstamp = Date.now(); - return this; -}; - -DomainCacheEntry.prototype.dispose = function() { - this.domain = ''; - if ( domainCacheEntryJunkyard.length < domainCacheEntryJunkyardMax ) { - domainCacheEntryJunkyard.push(this); - } -}; - -var domainCacheEntryFactory = function(domain) { - var entry = domainCacheEntryJunkyard.pop(); - if ( entry ) { - return entry.init(domain); - } - return new DomainCacheEntry(domain); -}; - -var domainCacheAdd = function(hostname, domain) { - var entry = domainCache.get(hostname); - if ( entry !== undefined ) { - entry.tstamp = Date.now(); - } else { - domainCache.set(hostname, domainCacheEntryFactory(domain)); - if ( domainCache.size === domainCacheCountHighWaterMark ) { - domainCachePrune(); - } - } - return domain; -}; - -var domainCacheEntrySort = function(a, b) { - return domainCache.get(b).tstamp - domainCache.get(a).tstamp; -}; - -var domainCachePrune = function() { - var hostnames = Array.from(domainCache.keys()) - .sort(domainCacheEntrySort) - .slice(domainCacheCountLowWaterMark); - var i = hostnames.length; - var hostname; - while ( i-- ) { - hostname = hostnames[i]; - domainCache.get(hostname).dispose(); - domainCache.delete(hostname); - } -}; - -var domainCacheReset = function() { - domainCache.clear(); -}; - -psl.onChanged.addListener(domainCacheReset); + DomainCacheEntry.prototype.dispose = function() { + this.domain = ''; + if ( domainCacheEntryJunkyard.length < domainCacheEntryJunkyardMax ) { + domainCacheEntryJunkyard.push(this); + } + }; -/******************************************************************************/ + var domainCacheEntryFactory = function(domain) { + var entry = domainCacheEntryJunkyard.pop(); + if ( entry ) { + return entry.init(domain); + } + return new DomainCacheEntry(domain); + }; + + var domainCacheAdd = function(hostname, domain) { + var entry = domainCache.get(hostname); + if ( entry !== undefined ) { + entry.tstamp = Date.now(); + } else { + domainCache.set(hostname, domainCacheEntryFactory(domain)); + if ( domainCache.size === domainCacheCountHighWaterMark ) { + domainCachePrune(); + } + } + return domain; + }; + + var domainCacheEntrySort = function(a, b) { + return domainCache.get(b).tstamp - domainCache.get(a).tstamp; + }; + + var domainCachePrune = function() { + var hostnames = Array.from(domainCache.keys()) + .sort(domainCacheEntrySort) + .slice(domainCacheCountLowWaterMark); + var i = hostnames.length; + var hostname; + while ( i-- ) { + hostname = hostnames[i]; + domainCache.get(hostname).dispose(); + domainCache.delete(hostname); + } + }; -URI.domainFromURI = function(uri) { - if ( !uri ) { - return ''; - } - return this.domainFromHostname(this.hostnameFromURI(uri)); -}; + var domainCacheReset = function() { + domainCache.clear(); + }; -/******************************************************************************/ + psl.onChanged.addListener(domainCacheReset); -// Normalize the way ηMatrix expects it + /******************************************************************************/ -URI.normalizedURI = function() { - // Will be removed: - // - port - // - user id/password - // - fragment - return this.assemble(this.normalizeBits); -}; + URI.domainFromURI = function(uri) { + if ( !uri ) { + return ''; + } + return this.domainFromHostname(this.hostnameFromURI(uri)); + }; -/******************************************************************************/ + /******************************************************************************/ -URI.rootURL = function() { - if ( !this.hostname ) { - return ''; - } - return this.assemble(this.schemeBit | this.hostnameBit); -}; + // Normalize the way ηMatrix expects it -/******************************************************************************/ + URI.normalizedURI = function() { + // Will be removed: + // - port + // - user id/password + // - fragment + return this.assemble(this.normalizeBits); + }; -URI.isValidHostname = function(hostname) { - var r; - try { - r = reValidHostname.test(hostname); - } - catch (e) { - return false; - } - return r; -}; + /******************************************************************************/ -/******************************************************************************/ + URI.rootURL = function() { + if ( !this.hostname ) { + return ''; + } + return this.assemble(this.schemeBit | this.hostnameBit); + }; -// Return the parent domain. For IP address, there is no parent domain. + /******************************************************************************/ -URI.parentHostnameFromHostname = function(hostname) { - // `locahost` => `` - // `example.org` => `example.org` - // `www.example.org` => `example.org` - // `tomato.www.example.org` => `example.org` - var domain = this.domainFromHostname(hostname); + URI.isValidHostname = function(hostname) { + var r; + try { + r = reValidHostname.test(hostname); + } + catch (e) { + return false; + } + return r; + }; + + /******************************************************************************/ + + // Return the parent domain. For IP address, there is no parent domain. + + URI.parentHostnameFromHostname = function(hostname) { + // `locahost` => `` + // `example.org` => `example.org` + // `www.example.org` => `example.org` + // `tomato.www.example.org` => `example.org` + var domain = this.domainFromHostname(hostname); + + // `locahost` === `` => bye + // `example.org` === `example.org` => bye + // `www.example.org` !== `example.org` => stay + // `tomato.www.example.org` !== `example.org` => stay + if ( domain === '' || domain === hostname ) { + return undefined; + } - // `locahost` === `` => bye - // `example.org` === `example.org` => bye - // `www.example.org` !== `example.org` => stay - // `tomato.www.example.org` !== `example.org` => stay - if ( domain === '' || domain === hostname ) { - return undefined; - } + // Parent is hostname minus first label + return hostname.slice(hostname.indexOf('.') + 1); + }; - // Parent is hostname minus first label - return hostname.slice(hostname.indexOf('.') + 1); -}; + /******************************************************************************/ -/******************************************************************************/ + // Return all possible parent hostnames which can be derived from `hostname`, + // ordered from direct parent up to domain inclusively. -// Return all possible parent hostnames which can be derived from `hostname`, -// ordered from direct parent up to domain inclusively. - -URI.parentHostnamesFromHostname = function(hostname) { - // TODO: I should create an object which is optimized to receive - // the list of hostnames by making it reusable (junkyard etc.) and which - // has its own element counter property in order to avoid memory - // alloc/dealloc. - var domain = this.domainFromHostname(hostname); - if ( domain === '' || domain === hostname ) { - return []; - } - var nodes = []; - var pos; - for (;;) { - pos = hostname.indexOf('.'); - if ( pos < 0 ) { - break; - } - hostname = hostname.slice(pos + 1); - nodes.push(hostname); - if ( hostname === domain ) { - break; - } - } - return nodes; -}; + URI.parentHostnamesFromHostname = function(hostname) { + // TODO: I should create an object which is optimized to receive + // the list of hostnames by making it reusable (junkyard etc.) and which + // has its own element counter property in order to avoid memory + // alloc/dealloc. + var domain = this.domainFromHostname(hostname); + if ( domain === '' || domain === hostname ) { + return []; + } + var nodes = []; + var pos; + for (;;) { + pos = hostname.indexOf('.'); + if ( pos < 0 ) { + break; + } + hostname = hostname.slice(pos + 1); + nodes.push(hostname); + if ( hostname === domain ) { + break; + } + } + return nodes; + }; -/******************************************************************************/ + /******************************************************************************/ -// Return all possible hostnames which can be derived from `hostname`, -// ordered from self up to domain inclusively. + // Return all possible hostnames which can be derived from `hostname`, + // ordered from self up to domain inclusively. -URI.allHostnamesFromHostname = function(hostname) { - var nodes = this.parentHostnamesFromHostname(hostname); - nodes.unshift(hostname); - return nodes; -}; + URI.allHostnamesFromHostname = function(hostname) { + var nodes = this.parentHostnamesFromHostname(hostname); + nodes.unshift(hostname); + return nodes; + }; -/******************************************************************************/ + /******************************************************************************/ -URI.toString = function() { - return this.assemble(); -}; + URI.toString = function() { + return this.assemble(); + }; -/******************************************************************************/ + /******************************************************************************/ -// Export + // Export -return URI; + return URI; -/******************************************************************************/ + /******************************************************************************/ })(); /******************************************************************************/ - diff --git a/js/user-rules.js b/js/user-rules.js index 3197284..02ff787 100644 --- a/js/user-rules.js +++ b/js/user-rules.js @@ -29,314 +29,313 @@ (function() { -/******************************************************************************/ - -// Switches before, rules after + /******************************************************************************/ -var directiveSort = function(a, b) { - var aIsSwitch = a.indexOf(':') !== -1; - var bIsSwitch = b.indexOf(':') !== -1; - if ( aIsSwitch === bIsSwitch ) { - return a.localeCompare(b); - } - return aIsSwitch ? -1 : 1; -}; + // Switches before, rules after -/******************************************************************************/ + var directiveSort = function(a, b) { + var aIsSwitch = a.indexOf(':') !== -1; + var bIsSwitch = b.indexOf(':') !== -1; + if ( aIsSwitch === bIsSwitch ) { + return a.localeCompare(b); + } + return aIsSwitch ? -1 : 1; + }; -var processUserRules = function(response) { - var rules, rule, i; - var allRules = {}; - var permanentRules = {}; - var temporaryRules = {}; - var onLeft, onRight; - - rules = response.permanentRules.split(/\n+/); - i = rules.length; - while ( i-- ) { - rule = rules[i].trim(); - if ( rule.length !== 0 ) { - permanentRules[rule] = allRules[rule] = true; + /******************************************************************************/ + + var processUserRules = function(response) { + var rules, rule, i; + var allRules = {}; + var permanentRules = {}; + var temporaryRules = {}; + var onLeft, onRight; + + rules = response.permanentRules.split(/\n+/); + i = rules.length; + while ( i-- ) { + rule = rules[i].trim(); + if ( rule.length !== 0 ) { + permanentRules[rule] = allRules[rule] = true; + } } - } - rules = response.temporaryRules.split(/\n+/); - i = rules.length; - while ( i-- ) { - rule = rules[i].trim(); - if ( rule.length !== 0 ) { - temporaryRules[rule] = allRules[rule] = true; + rules = response.temporaryRules.split(/\n+/); + i = rules.length; + while ( i-- ) { + rule = rules[i].trim(); + if ( rule.length !== 0 ) { + temporaryRules[rule] = allRules[rule] = true; + } } - } - var permanentList = document.createDocumentFragment(), - temporaryList = document.createDocumentFragment(), - li; - - rules = Object.keys(allRules).sort(directiveSort); - for ( i = 0; i < rules.length; i++ ) { - rule = rules[i]; - onLeft = permanentRules.hasOwnProperty(rule); - onRight = temporaryRules.hasOwnProperty(rule); - if ( onLeft && onRight ) { - li = document.createElement('li'); - li.textContent = rule; - permanentList.appendChild(li); - li = document.createElement('li'); - li.textContent = rule; - temporaryList.appendChild(li); - } else if ( onLeft ) { - li = document.createElement('li'); - li.textContent = rule; - permanentList.appendChild(li); - li = document.createElement('li'); - li.textContent = rule; - li.className = 'notRight toRemove'; - temporaryList.appendChild(li); - } else if ( onRight ) { - li = document.createElement('li'); - li.textContent = '\xA0'; - permanentList.appendChild(li); - li = document.createElement('li'); - li.textContent = rule; - li.className = 'notLeft'; - temporaryList.appendChild(li); + var permanentList = document.createDocumentFragment(), + temporaryList = document.createDocumentFragment(), + li; + + rules = Object.keys(allRules).sort(directiveSort); + for ( i = 0; i < rules.length; i++ ) { + rule = rules[i]; + onLeft = permanentRules.hasOwnProperty(rule); + onRight = temporaryRules.hasOwnProperty(rule); + if ( onLeft && onRight ) { + li = document.createElement('li'); + li.textContent = rule; + permanentList.appendChild(li); + li = document.createElement('li'); + li.textContent = rule; + temporaryList.appendChild(li); + } else if ( onLeft ) { + li = document.createElement('li'); + li.textContent = rule; + permanentList.appendChild(li); + li = document.createElement('li'); + li.textContent = rule; + li.className = 'notRight toRemove'; + temporaryList.appendChild(li); + } else if ( onRight ) { + li = document.createElement('li'); + li.textContent = '\xA0'; + permanentList.appendChild(li); + li = document.createElement('li'); + li.textContent = rule; + li.className = 'notLeft'; + temporaryList.appendChild(li); + } } - } - // TODO: build incrementally. + // TODO: build incrementally. - uDom('#diff > .left > ul > li').remove(); - document.querySelector('#diff > .left > ul').appendChild(permanentList); - uDom('#diff > .right > ul > li').remove(); - document.querySelector('#diff > .right > ul').appendChild(temporaryList); - uDom('#diff').toggleClass('dirty', response.temporaryRules !== response.permanentRules); -}; + uDom('#diff > .left > ul > li').remove(); + document.querySelector('#diff > .left > ul').appendChild(permanentList); + uDom('#diff > .right > ul > li').remove(); + document.querySelector('#diff > .right > ul').appendChild(temporaryList); + uDom('#diff').toggleClass('dirty', response.temporaryRules !== response.permanentRules); + }; -/******************************************************************************/ + /******************************************************************************/ -// https://github.com/chrisaljoudi/uBlock/issues/757 -// Support RequestPolicy rule syntax + // https://github.com/chrisaljoudi/uBlock/issues/757 + // Support RequestPolicy rule syntax -var fromRequestPolicy = function(content) { - var matches = /\[origins-to-destinations\]([^\[]+)/.exec(content); - if ( matches === null || matches.length !== 2 ) { - return; - } - return matches[1].trim() - .replace(/\|/g, ' ') - .replace(/\n/g, ' * allow\n'); -}; + var fromRequestPolicy = function(content) { + var matches = /\[origins-to-destinations\]([^\[]+)/.exec(content); + if ( matches === null || matches.length !== 2 ) { + return; + } + return matches[1].trim() + .replace(/\|/g, ' ') + .replace(/\n/g, ' * allow\n'); + }; -/******************************************************************************/ + /******************************************************************************/ -// https://github.com/gorhill/uMatrix/issues/270 + // https://github.com/gorhill/uMatrix/issues/270 -var fromNoScript = function(content) { - var noscript = null; - try { - noscript = JSON.parse(content); - } catch (e) { - } - if ( - noscript === null || - typeof noscript !== 'object' || - typeof noscript.prefs !== 'object' || - typeof noscript.prefs.clearClick === 'undefined' || - typeof noscript.whitelist !== 'string' || - typeof noscript.V !== 'string' - ) { - return; - } - var out = new Set(); - var reBad = /[a-z]+:\w*$/; - var reURL = /[a-z]+:\/\/([0-9a-z.-]+)/; - var directives = noscript.whitelist.split(/\s+/); - var i = directives.length; - var directive, matches; - while ( i-- ) { - directive = directives[i].trim(); - if ( directive === '' ) { - continue; + var fromNoScript = function(content) { + var noscript = null; + try { + noscript = JSON.parse(content); + } catch (e) { } - if ( reBad.test(directive) ) { - continue; + if ( + noscript === null || + typeof noscript !== 'object' || + typeof noscript.prefs !== 'object' || + typeof noscript.prefs.clearClick === 'undefined' || + typeof noscript.whitelist !== 'string' || + typeof noscript.V !== 'string' + ) { + return; } - matches = reURL.exec(directive); - if ( matches !== null ) { - directive = matches[1]; + var out = new Set(); + var reBad = /[a-z]+:\w*$/; + var reURL = /[a-z]+:\/\/([0-9a-z.-]+)/; + var directives = noscript.whitelist.split(/\s+/); + var i = directives.length; + var directive, matches; + while ( i-- ) { + directive = directives[i].trim(); + if ( directive === '' ) { + continue; + } + if ( reBad.test(directive) ) { + continue; + } + matches = reURL.exec(directive); + if ( matches !== null ) { + directive = matches[1]; + } + out.add('* ' + directive + ' * allow'); + out.add('* ' + directive + ' script allow'); + out.add('* ' + directive + ' frame allow'); } - out.add('* ' + directive + ' * allow'); - out.add('* ' + directive + ' script allow'); - out.add('* ' + directive + ' frame allow'); - } - return Array.from(out).join('\n'); -}; + return Array.from(out).join('\n'); + }; -/******************************************************************************/ + /******************************************************************************/ -var handleImportFilePicker = function() { - var fileReaderOnLoadHandler = function() { - if ( typeof this.result !== 'string' || this.result === '' ) { - return; - } - var result = fromRequestPolicy(this.result); - if ( result === undefined ) { - result = fromNoScript(this.result); + var handleImportFilePicker = function() { + var fileReaderOnLoadHandler = function() { + if ( typeof this.result !== 'string' || this.result === '' ) { + return; + } + var result = fromRequestPolicy(this.result); if ( result === undefined ) { - result = this.result; + result = fromNoScript(this.result); + if ( result === undefined ) { + result = this.result; + } } - } - if ( this.result === '' ) { return; } - var request = { - 'what': 'setUserRules', - 'temporaryRules': rulesFromHTML('#diff .right li') + '\n' + result + if ( this.result === '' ) { return; } + var request = { + 'what': 'setUserRules', + 'temporaryRules': rulesFromHTML('#diff .right li') + '\n' + result + }; + vAPI.messaging.send('user-rules.js', request, processUserRules); }; - vAPI.messaging.send('user-rules.js', request, processUserRules); + var file = this.files[0]; + if ( file === undefined || file.name === '' ) { + return; + } + if ( file.type.indexOf('text') !== 0 && file.type !== 'application/json') { + return; + } + var fr = new FileReader(); + fr.onload = fileReaderOnLoadHandler; + fr.readAsText(file); }; - var file = this.files[0]; - if ( file === undefined || file.name === '' ) { - return; - } - if ( file.type.indexOf('text') !== 0 && file.type !== 'application/json') { - return; - } - var fr = new FileReader(); - fr.onload = fileReaderOnLoadHandler; - fr.readAsText(file); -}; -/******************************************************************************/ + /******************************************************************************/ -var startImportFilePicker = function() { - var input = document.getElementById('importFilePicker'); - // Reset to empty string, this will ensure an change event is properly - // triggered if the user pick a file, even if it is the same as the last - // one picked. - input.value = ''; - input.click(); -}; - -/******************************************************************************/ + var startImportFilePicker = function() { + var input = document.getElementById('importFilePicker'); + // Reset to empty string, this will ensure an change event is properly + // triggered if the user pick a file, even if it is the same as the last + // one picked. + input.value = ''; + input.click(); + }; -function exportUserRulesToFile() { - vAPI.download({ - 'url': 'data:text/plain,' + encodeURIComponent(rulesFromHTML('#diff .left li') + '\n'), - 'filename': uDom('[data-i18n="userRulesDefaultFileName"]').text() - }); -} + /******************************************************************************/ -/******************************************************************************/ + function exportUserRulesToFile() { + vAPI.download({ + 'url': 'data:text/plain,' + encodeURIComponent(rulesFromHTML('#diff .left li') + '\n'), + 'filename': uDom('[data-i18n="userRulesDefaultFileName"]').text() + }); + } -var rulesFromHTML = function(selector) { - var rules = []; - var lis = uDom(selector); - var li; - for ( var i = 0; i < lis.length; i++ ) { - li = lis.at(i); - if ( li.hasClassName('toRemove') ) { - rules.push(''); - } else { - rules.push(li.text()); + /******************************************************************************/ + + var rulesFromHTML = function(selector) { + var rules = []; + var lis = uDom(selector); + var li; + for ( var i = 0; i < lis.length; i++ ) { + li = lis.at(i); + if ( li.hasClassName('toRemove') ) { + rules.push(''); + } else { + rules.push(li.text()); + } } - } - return rules.join('\n'); -}; + return rules.join('\n'); + }; -/******************************************************************************/ + /******************************************************************************/ -var revertHandler = function() { - var request = { - 'what': 'setUserRules', - 'temporaryRules': rulesFromHTML('#diff .left li') + var revertHandler = function() { + var request = { + 'what': 'setUserRules', + 'temporaryRules': rulesFromHTML('#diff .left li') + }; + vAPI.messaging.send('user-rules.js', request, processUserRules); }; - vAPI.messaging.send('user-rules.js', request, processUserRules); -}; -/******************************************************************************/ + /******************************************************************************/ -var commitHandler = function() { - var request = { - 'what': 'setUserRules', - 'permanentRules': rulesFromHTML('#diff .right li') + var commitHandler = function() { + var request = { + 'what': 'setUserRules', + 'permanentRules': rulesFromHTML('#diff .right li') + }; + vAPI.messaging.send('user-rules.js', request, processUserRules); }; - vAPI.messaging.send('user-rules.js', request, processUserRules); -}; -/******************************************************************************/ + /******************************************************************************/ -var editStartHandler = function() { - uDom('#diff .right textarea').val(rulesFromHTML('#diff .right li')); - var parent = uDom(this).ancestors('#diff'); - parent.toggleClass('edit', true); -}; + var editStartHandler = function() { + uDom('#diff .right textarea').val(rulesFromHTML('#diff .right li')); + var parent = uDom(this).ancestors('#diff'); + parent.toggleClass('edit', true); + }; -/******************************************************************************/ + /******************************************************************************/ -var editStopHandler = function() { - var parent = uDom(this).ancestors('#diff'); - parent.toggleClass('edit', false); - var request = { - 'what': 'setUserRules', - 'temporaryRules': uDom('#diff .right textarea').val() + var editStopHandler = function() { + var parent = uDom(this).ancestors('#diff'); + parent.toggleClass('edit', false); + var request = { + 'what': 'setUserRules', + 'temporaryRules': uDom('#diff .right textarea').val() + }; + vAPI.messaging.send('user-rules.js', request, processUserRules); }; - vAPI.messaging.send('user-rules.js', request, processUserRules); -}; -/******************************************************************************/ + /******************************************************************************/ -var editCancelHandler = function() { - var parent = uDom(this).ancestors('#diff'); - parent.toggleClass('edit', false); -}; + var editCancelHandler = function() { + var parent = uDom(this).ancestors('#diff'); + parent.toggleClass('edit', false); + }; -/******************************************************************************/ + /******************************************************************************/ -var temporaryRulesToggler = function() { - var li = uDom(this); - li.toggleClass('toRemove'); - var request = { - 'what': 'setUserRules', - 'temporaryRules': rulesFromHTML('#diff .right li') + var temporaryRulesToggler = function() { + var li = uDom(this); + li.toggleClass('toRemove'); + var request = { + 'what': 'setUserRules', + 'temporaryRules': rulesFromHTML('#diff .right li') + }; + vAPI.messaging.send('user-rules.js', request, processUserRules); }; - vAPI.messaging.send('user-rules.js', request, processUserRules); -}; - -/******************************************************************************/ -self.cloud.onPush = function() { - return rulesFromHTML('#diff .left li'); -}; + /******************************************************************************/ -self.cloud.onPull = function(data, append) { - if ( typeof data !== 'string' ) { return; } - if ( append ) { - data = rulesFromHTML('#diff .right li') + '\n' + data; - } - var request = { - 'what': 'setUserRules', - 'temporaryRules': data + self.cloud.onPush = function() { + return rulesFromHTML('#diff .left li'); }; - vAPI.messaging.send('user-rules.js', request, processUserRules); -}; -/******************************************************************************/ + self.cloud.onPull = function(data, append) { + if ( typeof data !== 'string' ) { return; } + if ( append ) { + data = rulesFromHTML('#diff .right li') + '\n' + data; + } + var request = { + 'what': 'setUserRules', + 'temporaryRules': data + }; + vAPI.messaging.send('user-rules.js', request, processUserRules); + }; -uDom.onLoad(function() { - // Handle user interaction - uDom('#importButton').on('click', startImportFilePicker); - uDom('#importFilePicker').on('change', handleImportFilePicker); - uDom('#exportButton').on('click', exportUserRulesToFile); - uDom('#revertButton').on('click', revertHandler); - uDom('#commitButton').on('click', commitHandler); - uDom('#editEnterButton').on('click', editStartHandler); - uDom('#editStopButton').on('click', editStopHandler); - uDom('#editCancelButton').on('click', editCancelHandler); - uDom('#diff > .right > ul').on('click', 'li', temporaryRulesToggler); - - vAPI.messaging.send('user-rules.js', { what: 'getUserRules' }, processUserRules); -}); + /******************************************************************************/ + + uDom.onLoad(function() { + // Handle user interaction + uDom('#importButton').on('click', startImportFilePicker); + uDom('#importFilePicker').on('change', handleImportFilePicker); + uDom('#exportButton').on('click', exportUserRulesToFile); + uDom('#revertButton').on('click', revertHandler); + uDom('#commitButton').on('click', commitHandler); + uDom('#editEnterButton').on('click', editStartHandler); + uDom('#editStopButton').on('click', editStopHandler); + uDom('#editCancelButton').on('click', editCancelHandler); + uDom('#diff > .right > ul').on('click', 'li', temporaryRulesToggler); + + vAPI.messaging.send('user-rules.js', { what: 'getUserRules' }, processUserRules); + }); -/******************************************************************************/ + /******************************************************************************/ })(); - diff --git a/js/usersettings.js b/js/usersettings.js index e6b3e6e..e2655b6 100644 --- a/js/usersettings.js +++ b/js/usersettings.js @@ -41,8 +41,8 @@ // Pre-change switch ( name ) { - - default: + + default: break; } @@ -51,8 +51,8 @@ // Post-change switch ( name ) { - - default: + + default: break; } diff --git a/js/vapi-background.js b/js/vapi-background.js index 1064153..7686bc9 100644 --- a/js/vapi-background.js +++ b/js/vapi-background.js @@ -35,53 +35,53 @@ // Icon-related stuff vAPI.setIcon = function (tabId, iconId, badge) { - // If badge is undefined, then setIcon was called from the - // TabSelect event - let win; - if (badge === undefined) { + // If badge is undefined, then setIcon was called from the + // TabSelect event + let win; + if (badge === undefined) { win = iconId; - } else { + } else { win = vAPI.window.getCurrentWindow(); - } - - let tabBrowser = vAPI.browser.getTabBrowser(win); - if (tabBrowser === null) { + } + + let tabBrowser = vAPI.browser.getTabBrowser(win); + if (tabBrowser === null) { return; - } - - let curTabId = vAPI.tabs.manager.tabIdFromTarget(tabBrowser.selectedTab); - let tb = vAPI.toolbarButton; + } + + let curTabId = vAPI.tabs.manager.tabIdFromTarget(tabBrowser.selectedTab); + let tb = vAPI.toolbarButton; - // from 'TabSelect' event - if (tabId === undefined) { + // from 'TabSelect' event + if (tabId === undefined) { tabId = curTabId; - } else if (badge !== undefined) { + } else if (badge !== undefined) { tb.tabs[tabId] = { - badge: badge, img: iconId - }; - } + badge: badge, img: iconId + }; + } - if (tabId === curTabId) { + if (tabId === curTabId) { tb.updateState(win, tabId); - } + } }; vAPI.httpObserver = { - classDescription: 'net-channel-event-sinks for ' + location.host, - classID: Components.ID('{5d2e2797-6d68-42e2-8aeb-81ce6ba16b95}'), - contractID: '@' + location.host + '/net-channel-event-sinks;1', - REQDATAKEY: location.host + 'reqdata', - ABORT: Components.results.NS_BINDING_ABORTED, - ACCEPT: Components.results.NS_SUCCEEDED, - // Request types: - // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIContentPolicy#Constants - // eMatrix: we can just use nsIContentPolicy's built-in - // constants, can we? - frameTypeMap: { + classDescription: 'net-channel-event-sinks for ' + location.host, + classID: Components.ID('{5d2e2797-6d68-42e2-8aeb-81ce6ba16b95}'), + contractID: '@' + location.host + '/net-channel-event-sinks;1', + REQDATAKEY: location.host + 'reqdata', + ABORT: Components.results.NS_BINDING_ABORTED, + ACCEPT: Components.results.NS_SUCCEEDED, + // Request types: + // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIContentPolicy#Constants + // eMatrix: we can just use nsIContentPolicy's built-in + // constants, can we? + frameTypeMap: { 6: 'main_frame', 7: 'sub_frame' - }, - typeMap: { + }, + typeMap: { 1: 'other', 2: 'script', 3: 'image', @@ -103,359 +103,359 @@ 20: 'xmlhttprequest', 21: 'imageset', 22: 'web_manifest' - }, - mimeTypeMap: { + }, + mimeTypeMap: { 'audio': 15, 'video': 15 - }, - get componentRegistrar () { + }, + get componentRegistrar () { return Components.manager.QueryInterface(Ci.nsIComponentRegistrar); - }, - get categoryManager () { + }, + get categoryManager () { return Cc['@mozilla.org/categorymanager;1'] .getService(Ci.nsICategoryManager); - }, - QueryInterface: (function () { + }, + QueryInterface: (function () { var {XPCOMUtils} = - Cu.import('resource://gre/modules/XPCOMUtils.jsm', null); + Cu.import('resource://gre/modules/XPCOMUtils.jsm', null); return XPCOMUtils.generateQI([ - Ci.nsIFactory, - Ci.nsIObserver, - Ci.nsIChannelEventSink, - Ci.nsISupportsWeakReference + Ci.nsIFactory, + Ci.nsIObserver, + Ci.nsIChannelEventSink, + Ci.nsISupportsWeakReference ]); - })(), - createInstance: function (outer, iid) { + })(), + createInstance: function (outer, iid) { if (outer) { - throw Components.results.NS_ERROR_NO_AGGREGATION; + throw Components.results.NS_ERROR_NO_AGGREGATION; } return this.QueryInterface(iid); - }, - register: function () { + }, + register: function () { this.pendingRingBufferInit(); - + Services.obs.addObserver(this, 'http-on-modify-request', true); Services.obs.addObserver(this, 'http-on-examine-response', true); Services.obs.addObserver(this, 'http-on-examine-cached-response', true); // Guard against stale instances not having been unregistered if (this.componentRegistrar.isCIDRegistered(this.classID)) { - try { + try { this.componentRegistrar - .unregisterFactory(this.classID, - Components.manager - .getClassObject(this.classID, - Ci.nsIFactory)); - } catch (ex) { + .unregisterFactory(this.classID, + Components.manager + .getClassObject(this.classID, + Ci.nsIFactory)); + } catch (ex) { console.error('eMatrix> httpObserver > ' - +'unable to unregister stale instance: ', ex); - } + +'unable to unregister stale instance: ', ex); + } } this.componentRegistrar.registerFactory(this.classID, - this.classDescription, - this.contractID, - this); + this.classDescription, + this.contractID, + this); this.categoryManager.addCategoryEntry('net-channel-event-sinks', - this.contractID, - this.contractID, - false, - true); - }, - unregister: function () { + this.contractID, + this.contractID, + false, + true); + }, + unregister: function () { Services.obs.removeObserver(this, 'http-on-modify-request'); Services.obs.removeObserver(this, 'http-on-examine-response'); Services.obs.removeObserver(this, 'http-on-examine-cached-response'); this.componentRegistrar.unregisterFactory(this.classID, this); this.categoryManager.deleteCategoryEntry('net-channel-event-sinks', - this.contractID, - false); - }, - PendingRequest: function () { + this.contractID, + false); + }, + PendingRequest: function () { this.rawType = 0; this.tabId = 0; this._key = ''; // key is url, from URI.spec - }, - // If all work fine, this map should not grow indefinitely. It - // can have stale items in it, but these will be taken care of - // when entries in the ring buffer are overwritten. - pendingURLToIndex: new Map(), - pendingWritePointer: 0, - pendingRingBuffer: new Array(256), - pendingRingBufferInit: function () { + }, + // If all work fine, this map should not grow indefinitely. It + // can have stale items in it, but these will be taken care of + // when entries in the ring buffer are overwritten. + pendingURLToIndex: new Map(), + pendingWritePointer: 0, + pendingRingBuffer: new Array(256), + pendingRingBufferInit: function () { // Use and reuse pre-allocated PendingRequest objects = // less memory churning. for (let i=this.pendingRingBuffer.length-1; i>=0; --i) { - this.pendingRingBuffer[i] = new this.PendingRequest(); + this.pendingRingBuffer[i] = new this.PendingRequest(); } - }, - createPendingRequest: function (url) { - // Pending request ring buffer: - // +-------+-------+-------+-------+-------+-------+------- - // |0 |1 |2 |3 |4 |5 |... - // +-------+-------+-------+-------+-------+-------+------- - // - // URL to ring buffer index map: - // { k = URL, s = ring buffer indices } - // - // s is a string which character codes map to ring buffer - // indices -- for when the same URL is received multiple times - // by shouldLoadListener() before the existing one is serviced - // by the network request observer. I believe the use of a - // string in lieu of an array reduces memory churning. + }, + createPendingRequest: function (url) { + // Pending request ring buffer: + // +-------+-------+-------+-------+-------+-------+------- + // |0 |1 |2 |3 |4 |5 |... + // +-------+-------+-------+-------+-------+-------+------- + // + // URL to ring buffer index map: + // { k = URL, s = ring buffer indices } + // + // s is a string which character codes map to ring buffer + // indices -- for when the same URL is received multiple times + // by shouldLoadListener() before the existing one is serviced + // by the network request observer. I believe the use of a + // string in lieu of an array reduces memory churning. let bucket; let i = this.pendingWritePointer; this.pendingWritePointer = i + 1 & 255; - + let preq = this.pendingRingBuffer[i]; let si = String.fromCharCode(i); - + // Cleanup unserviced pending request if (preq._key !== '') { - bucket = this.pendingURLToIndex.get(preq._key); - if (bucket.length === 1) { + bucket = this.pendingURLToIndex.get(preq._key); + if (bucket.length === 1) { this.pendingURLToIndex.delete(preq._key); - } else { + } else { let pos = bucket.indexOf(si); this.pendingURLToIndex.set(preq._key, - bucket.slice(0, pos) - + bucket.slice(pos + 1)); - } + bucket.slice(0, pos) + + bucket.slice(pos + 1)); + } } - + bucket = this.pendingURLToIndex.get(url); this.pendingURLToIndex.set(url, bucket === undefined - ? si - : bucket + si); + ? si + : bucket + si); preq._key = url; return preq; - }, - lookupPendingRequest: function (url) { + }, + lookupPendingRequest: function (url) { let bucket = this.pendingURLToIndex.get(url); if (bucket === undefined) { - return null; + return null; } - + let i = bucket.charCodeAt(0); if (bucket.length === 1) { - this.pendingURLToIndex.delete(url); + this.pendingURLToIndex.delete(url); } else { - this.pendingURLToIndex.set(url, bucket.slice(1)); + this.pendingURLToIndex.set(url, bucket.slice(1)); } - + let preq = this.pendingRingBuffer[i]; preq._key = ''; // mark as "serviced" return preq; - }, - handleRequest: function (channel, URI, tabId, rawType) { + }, + handleRequest: function (channel, URI, tabId, rawType) { let type = this.typeMap[rawType] || 'other'; let onBeforeRequest = vAPI.net.onBeforeRequest; if (onBeforeRequest.types === null - || onBeforeRequest.types.has(type)) { - let result = onBeforeRequest.callback({ + || onBeforeRequest.types.has(type)) { + let result = onBeforeRequest.callback({ parentFrameId: type === 'main_frame' ? -1 : 0, tabId: tabId, type: type, url: URI.asciiSpec - }); + }); - if (typeof result === 'object') { + if (typeof result === 'object') { channel.cancel(this.ABORT); return true; - } + } } let onBeforeSendHeaders = vAPI.net.onBeforeSendHeaders; if (onBeforeSendHeaders.types === null - || onBeforeSendHeaders.types.has(type)) { - let requestHeaders = HTTPRequestHeaders.factory(channel); - let newHeaders = onBeforeSendHeaders.callback({ + || onBeforeSendHeaders.types.has(type)) { + let requestHeaders = HTTPRequestHeaders.factory(channel); + let newHeaders = onBeforeSendHeaders.callback({ parentFrameId: type === 'main_frame' ? -1 : 0, requestHeaders: requestHeaders.headers, tabId: tabId, type: type, url: URI.asciiSpec, method: channel.requestMethod - }); - - if (newHeaders) { + }); + + if (newHeaders) { requestHeaders.update(); - } - requestHeaders.dispose(); + } + requestHeaders.dispose(); } return false; - }, - channelDataFromChannel: function (channel) { + }, + channelDataFromChannel: function (channel) { if (channel instanceof Ci.nsIWritablePropertyBag) { - try { + try { return channel.getProperty(this.REQDATAKEY) || null; - } catch (ex) { - // Ignore - } + } catch (ex) { + // Ignore + } } - + return null; - }, - // https://github.com/gorhill/uMatrix/issues/165 - // https://developer.mozilla.org/en-US/Firefox/Releases/3.5/Updating_extensions#Getting_a_load_context_from_a_request - // Not sure `ematrix:shouldLoad` is still needed, eMatrix does - // not care about embedded frames topography. - // Also: - // https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts - tabIdFromChannel: function (channel) { + }, + // https://github.com/gorhill/uMatrix/issues/165 + // https://developer.mozilla.org/en-US/Firefox/Releases/3.5/Updating_extensions#Getting_a_load_context_from_a_request + // Not sure `ematrix:shouldLoad` is still needed, eMatrix does + // not care about embedded frames topography. + // Also: + // https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Limitations_of_chrome_scripts + tabIdFromChannel: function (channel) { let lc; try { - lc = channel.notificationCallbacks.getInterface(Ci.nsILoadContext); + lc = channel.notificationCallbacks.getInterface(Ci.nsILoadContext); } catch(ex) { - // Ignore + // Ignore } - + if (!lc) { - try { + try { lc = channel.loadGroup.notificationCallbacks - .getInterface(Ci.nsILoadContext); - } catch(ex) { - // Ignore - } - - if (!lc) { + .getInterface(Ci.nsILoadContext); + } catch(ex) { + // Ignore + } + + if (!lc) { return vAPI.noTabId; - } + } } - + if (lc.topFrameElement) { - return vAPI.tabs.manager.tabIdFromTarget(lc.topFrameElement); + return vAPI.tabs.manager.tabIdFromTarget(lc.topFrameElement); } - + let win; try { - win = lc.associatedWindow; + win = lc.associatedWindow; } catch (ex) { - // Ignore - } - + // Ignore + } + if (!win) { - return vAPI.noTabId; + return vAPI.noTabId; } - + if (win.top) { - win = win.top; + win = win.top; } - + let tabBrowser; try { - tabBrowser = - vAPI.browser.getTabBrowser - (win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShell).rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow)); + tabBrowser = + vAPI.browser.getTabBrowser + (win.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell).rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow)); } catch (ex) { - // Ignore - } - + // Ignore + } + if (!tabBrowser) { - return vAPI.noTabId; + return vAPI.noTabId; } - + if (tabBrowser.getBrowserForContentWindow) { - return vAPI.tabs.manager - .tabIdFromTarget(tabBrowser.getBrowserForContentWindow(win)); + return vAPI.tabs.manager + .tabIdFromTarget(tabBrowser.getBrowserForContentWindow(win)); } - + // Falling back onto _getTabForContentWindow to ensure older // versions of Firefox work well. return tabBrowser._getTabForContentWindow - ? vAPI.tabs.manager - .tabIdFromTarget(tabBrowser._getTabForContentWindow(win)) - : vAPI.noTabId; - }, - rawtypeFromContentType: function (channel) { + ? vAPI.tabs.manager + .tabIdFromTarget(tabBrowser._getTabForContentWindow(win)) + : vAPI.noTabId; + }, + rawtypeFromContentType: function (channel) { let mime = channel.contentType; if (!mime) { - return 0; + return 0; } - + let pos = mime.indexOf('/'); if (pos === -1) { - pos = mime.length; + pos = mime.length; } - + return this.mimeTypeMap[mime.slice(0, pos)] || 0; - }, - observe: function (channel, topic) { + }, + observe: function (channel, topic) { if (channel instanceof Ci.nsIHttpChannel === false) { - return; + return; } let URI = channel.URI; let channelData = this.channelDataFromChannel(channel); if (topic.lastIndexOf('http-on-examine-', 0) === 0) { - if (channelData === null) { + if (channelData === null) { return; - } + } - let type = this.frameTypeMap[channelData[1]]; - if (!type) { + let type = this.frameTypeMap[channelData[1]]; + if (!type) { return; - } - - // topic = ['Content-Security-Policy', - // 'Content-Security-Policy-Report-Only']; - // - // Can send empty responseHeaders as these headers are - // only added to and then merged. - // - // TODO: Find better place for this, needs to be set - // before onHeadersReceived.callback. Web workers not - // blocked in Pale Moon as child-src currently - // unavailable, see: - // - // https://github.com/MoonchildProductions/Pale-Moon/issues/949 - // - // eMatrix: as of Pale Moon 28 it seems child-src is - // available and depracated(?) - if (ηMatrix.cspNoWorker === undefined) { + } + + // topic = ['Content-Security-Policy', + // 'Content-Security-Policy-Report-Only']; + // + // Can send empty responseHeaders as these headers are + // only added to and then merged. + // + // TODO: Find better place for this, needs to be set + // before onHeadersReceived.callback. Web workers not + // blocked in Pale Moon as child-src currently + // unavailable, see: + // + // https://github.com/MoonchildProductions/Pale-Moon/issues/949 + // + // eMatrix: as of Pale Moon 28 it seems child-src is + // available and depracated(?) + if (ηMatrix.cspNoWorker === undefined) { // ηMatrix.cspNoWorker = "child-src 'none'; " - // +"frame-src data: blob: *; " - // +"report-uri about:blank"; - ηMatrix.cspNoWorker = "worker-src 'none'; " - +"frame-src data: blob: *; " - +"report-uri about:blank"; - } - - let result = vAPI.net.onHeadersReceived.callback({ + // +"frame-src data: blob: *; " + // +"report-uri about:blank"; + ηMatrix.cspNoWorker = "worker-src 'none'; " + +"frame-src data: blob: *; " + +"report-uri about:blank"; + } + + let result = vAPI.net.onHeadersReceived.callback({ parentFrameId: type === 'main_frame' ? -1 : 0, responseHeaders: [], tabId: channelData[0], type: type, url: URI.asciiSpec - }); + }); - if (result) { + if (result) { for (let header of result.responseHeaders) { - channel.setResponseHeader(header.name, - header.value, - true); + channel.setResponseHeader(header.name, + header.value, + true); } - } - - return; + } + + return; } // http-on-modify-request // The channel was previously serviced. if (channelData !== null) { - this.handleRequest(channel, URI, - channelData[0], channelData[1]); - return; + this.handleRequest(channel, URI, + channelData[0], channelData[1]); + return; } // The channel was never serviced. @@ -466,105 +466,105 @@ // https://github.com/gorhill/uMatrix/issues/390#issuecomment-155717004 if (loadInfo) { - rawType = (loadInfo.externalContentPolicyType !== undefined) - ? loadInfo.externalContentPolicyType - : loadInfo.contentPolicyType; - if (!rawType) { + rawType = (loadInfo.externalContentPolicyType !== undefined) + ? loadInfo.externalContentPolicyType + : loadInfo.contentPolicyType; + if (!rawType) { rawType = 1; - } + } } if (pendingRequest !== null) { - tabId = pendingRequest.tabId; - // https://github.com/gorhill/uBlock/issues/654 - // Use the request type from the HTTP observer point of view. - if (rawType !== 1) { + tabId = pendingRequest.tabId; + // https://github.com/gorhill/uBlock/issues/654 + // Use the request type from the HTTP observer point of view. + if (rawType !== 1) { pendingRequest.rawType = rawType; - } else { + } else { rawType = pendingRequest.rawType; - } + } } else { - tabId = this.tabIdFromChannel(channel); + tabId = this.tabIdFromChannel(channel); } if (this.handleRequest(channel, URI, tabId, rawType)) { - return; + return; } if (channel instanceof Ci.nsIWritablePropertyBag === false) { - return; + return; } // Carry data for behind-the-scene redirects channel.setProperty(this.REQDATAKEY, [tabId, rawType]); - }, - asyncOnChannelRedirect: function (oldChannel, newChannel, - flags, callback) { - // contentPolicy.shouldLoad doesn't detect redirects, this - // needs to be used + }, + asyncOnChannelRedirect: function (oldChannel, newChannel, + flags, callback) { + // contentPolicy.shouldLoad doesn't detect redirects, this + // needs to be used // If error thrown, the redirect will fail try { - let URI = newChannel.URI; - if (!URI.schemeIs('http') && !URI.schemeIs('https')) { + let URI = newChannel.URI; + if (!URI.schemeIs('http') && !URI.schemeIs('https')) { return; - } + } - if (newChannel instanceof Ci.nsIWritablePropertyBag === false) { + if (newChannel instanceof Ci.nsIWritablePropertyBag === false) { return; - } + } - let channelData = this.channelDataFromChannel(oldChannel); - if (channelData === null) { + let channelData = this.channelDataFromChannel(oldChannel); + if (channelData === null) { return; - } + } - // Carry the data on in case of multiple redirects - newChannel.setProperty(this.REQDATAKEY, channelData); + // Carry the data on in case of multiple redirects + newChannel.setProperty(this.REQDATAKEY, channelData); } catch (ex) { - // console.error(ex); - // Ignore + // console.error(ex); + // Ignore } finally { - callback.onRedirectVerifyCallback(this.ACCEPT); + callback.onRedirectVerifyCallback(this.ACCEPT); } - } + } }; vAPI.toolbarButton = { - id: location.host + '-button', - type: 'view', - viewId: location.host + '-panel', - label: vAPI.app.name, - tooltiptext: vAPI.app.name, - tabs: {/*tabId: {badge: 0, img: boolean}*/}, - init: null, - codePath: '' + id: location.host + '-button', + type: 'view', + viewId: location.host + '-panel', + label: vAPI.app.name, + tooltiptext: vAPI.app.name, + tabs: {/*tabId: {badge: 0, img: boolean}*/}, + init: null, + codePath: '' }; // Non-Fennec: common code paths. (function () { - if (vAPI.fennec) { + if (vAPI.fennec) { return; - } + } - let tbb = vAPI.toolbarButton; - let popupCommittedWidth = 0; - let popupCommittedHeight = 0; + let tbb = vAPI.toolbarButton; + let popupCommittedWidth = 0; + let popupCommittedHeight = 0; - tbb.onViewShowing = function ({target}) { + tbb.onViewShowing = function ({target}) { popupCommittedWidth = popupCommittedHeight = 0; target.firstChild.setAttribute('src', vAPI.getURL('popup.html')); - }; + }; - tbb.onViewHiding = function ({target}) { + tbb.onViewHiding = function ({target}) { target.parentNode.style.maxWidth = ''; target.firstChild.setAttribute('src', 'about:blank'); - }; + }; - tbb.updateState = function (win, tabId) { + tbb.updateState = function (win, tabId) { let button = win.document.getElementById(this.id); if (!button) { - return; + return; } let icon = this.tabs[tabId]; @@ -574,9 +574,9 @@ let iconId = icon && icon.img ? icon.img : 'off'; icon = 'url(' + vAPI.getURL('img/browsericons/icon19-' + iconId + '.png') + ')'; button.style.listStyleImage = icon; - }; + }; - tbb.populatePanel = function (doc, panel) { + tbb.populatePanel = function (doc, panel) { panel.setAttribute('id', this.viewId); let iframe = doc.createElement('iframe'); @@ -585,135 +585,135 @@ panel.appendChild(iframe); let toPx = function (pixels) { - return pixels.toString() + 'px'; + return pixels.toString() + 'px'; }; let scrollBarWidth = 0; let resizeTimer = null; let resizePopupDelayed = function (attempts) { - if (resizeTimer !== null) { + if (resizeTimer !== null) { return false; - } + } - // Sanity check - attempts = (attempts || 0) + 1; - if ( attempts > 1/*000*/ ) { + // Sanity check + attempts = (attempts || 0) + 1; + if ( attempts > 1/*000*/ ) { //console.error('eMatrix> resizePopupDelayed: giving up after too many attempts'); return false; - } + } - resizeTimer = vAPI.setTimeout(resizePopup, 10, attempts); - return true; + resizeTimer = vAPI.setTimeout(resizePopup, 10, attempts); + return true; }; let resizePopup = function (attempts) { - resizeTimer = null; + resizeTimer = null; - panel.parentNode.style.maxWidth = 'none'; - let body = iframe.contentDocument.body; + panel.parentNode.style.maxWidth = 'none'; + let body = iframe.contentDocument.body; - // https://github.com/gorhill/uMatrix/issues/301 - // Don't resize if committed size did not change. - if (popupCommittedWidth === body.clientWidth - && popupCommittedHeight === body.clientHeight) { + // https://github.com/gorhill/uMatrix/issues/301 + // Don't resize if committed size did not change. + if (popupCommittedWidth === body.clientWidth + && popupCommittedHeight === body.clientHeight) { return; - } + } - // We set a limit for height - let height = Math.min(body.clientHeight, 600); + // We set a limit for height + let height = Math.min(body.clientHeight, 600); - // https://github.com/chrisaljoudi/uBlock/issues/730 - // Voodoo programming: this recipe works - panel.style.setProperty('height', toPx(height)); - iframe.style.setProperty('height', toPx(height)); + // https://github.com/chrisaljoudi/uBlock/issues/730 + // Voodoo programming: this recipe works + panel.style.setProperty('height', toPx(height)); + iframe.style.setProperty('height', toPx(height)); - // Adjust width for presence/absence of vertical scroll bar which may - // have appeared as a result of last operation. - let contentWindow = iframe.contentWindow; - let width = body.clientWidth; - if (contentWindow.scrollMaxY !== 0) { + // Adjust width for presence/absence of vertical scroll bar which may + // have appeared as a result of last operation. + let contentWindow = iframe.contentWindow; + let width = body.clientWidth; + if (contentWindow.scrollMaxY !== 0) { width += scrollBarWidth; - } - panel.style.setProperty('width', toPx(width)); + } + panel.style.setProperty('width', toPx(width)); - // scrollMaxX should always be zero once we know the scrollbar width - if (contentWindow.scrollMaxX !== 0) { + // scrollMaxX should always be zero once we know the scrollbar width + if (contentWindow.scrollMaxX !== 0) { scrollBarWidth = contentWindow.scrollMaxX; width += scrollBarWidth; panel.style.setProperty('width', toPx(width)); - } + } - if (iframe.clientHeight !== height - || panel.clientWidth !== width) { + if (iframe.clientHeight !== height + || panel.clientWidth !== width) { if (resizePopupDelayed(attempts)) { - return; + return; } // resizePopupDelayed won't be called again, so commit // dimentsions. - } + } - popupCommittedWidth = body.clientWidth; - popupCommittedHeight = body.clientHeight; + popupCommittedWidth = body.clientWidth; + popupCommittedHeight = body.clientHeight; }; let onResizeRequested = function () { - let body = iframe.contentDocument.body; - if (body.getAttribute('data-resize-popup') !== 'true') { + let body = iframe.contentDocument.body; + if (body.getAttribute('data-resize-popup') !== 'true') { return; - } - body.removeAttribute('data-resize-popup'); - resizePopupDelayed(); + } + body.removeAttribute('data-resize-popup'); + resizePopupDelayed(); }; let onPopupReady = function () { - let win = this.contentWindow; + let win = this.contentWindow; - if (!win || win.location.host !== location.host) { + if (!win || win.location.host !== location.host) { return; - } + } - if (typeof tbb.onBeforePopupReady === 'function') { + if (typeof tbb.onBeforePopupReady === 'function') { tbb.onBeforePopupReady.call(this); - } + } - resizePopupDelayed(); + resizePopupDelayed(); - let body = win.document.body; - body.removeAttribute('data-resize-popup'); - - let mutationObserver = - new win.MutationObserver(onResizeRequested); - - mutationObserver.observe(body, { + let body = win.document.body; + body.removeAttribute('data-resize-popup'); + + let mutationObserver = + new win.MutationObserver(onResizeRequested); + + mutationObserver.observe(body, { attributes: true, attributeFilter: [ 'data-resize-popup' ] - }); + }); }; iframe.addEventListener('load', onPopupReady, true); - }; + }; })(); /******************************************************************************/ (function () { - // Add toolbar button for not-Basilisk - if (Services.appinfo.ID === "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") { - return; - } - - let tbb = vAPI.toolbarButton; - if (tbb.init !== null) { + // Add toolbar button for not-Basilisk + if (Services.appinfo.ID === "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") { return; - } + } - tbb.codePath = 'legacy'; - tbb.viewId = tbb.id + '-panel'; + let tbb = vAPI.toolbarButton; + if (tbb.init !== null) { + return; + } - let styleSheetUri = null; + tbb.codePath = 'legacy'; + tbb.viewId = tbb.id + '-panel'; - let createToolbarButton = function (window) { + let styleSheetUri = null; + + let createToolbarButton = function (window) { let document = window.document; let toolbarButton = document.createElement('toolbarbutton'); @@ -722,40 +722,40 @@ toolbarButton.setAttribute('type', 'menu'); toolbarButton.setAttribute('removable', 'true'); toolbarButton.setAttribute('class', 'toolbarbutton-1 ' - +'chromeclass-toolbar-additional'); + +'chromeclass-toolbar-additional'); toolbarButton.setAttribute('label', tbb.label); - toolbarButton.setAttribute('tooltiptext', tbb.tooltiptext); + toolbarButton.setAttribute('tooltiptext', tbb.tooltiptext); let toolbarButtonPanel = document.createElement('panel'); // NOTE: Setting level to parent breaks the popup for PaleMoon under // linux (mouse pointer misaligned with content). For some reason. - // eMatrix: TODO check if it's still true + // eMatrix: TODO check if it's still true // toolbarButtonPanel.setAttribute('level', 'parent'); tbb.populatePanel(document, toolbarButtonPanel); toolbarButtonPanel.addEventListener('popupshowing', - tbb.onViewShowing); + tbb.onViewShowing); toolbarButtonPanel.addEventListener('popuphiding', - tbb.onViewHiding); + tbb.onViewHiding); toolbarButton.appendChild(toolbarButtonPanel); - toolbarButtonPanel.setAttribute('tooltiptext', ''); + toolbarButtonPanel.setAttribute('tooltiptext', ''); return toolbarButton; - }; + }; - let addLegacyToolbarButton = function (window) { + let addLegacyToolbarButton = function (window) { // eMatrix's stylesheet lazily added. if (styleSheetUri === null) { - var sss = Cc["@mozilla.org/content/style-sheet-service;1"] + var sss = Cc["@mozilla.org/content/style-sheet-service;1"] .getService(Ci.nsIStyleSheetService); - styleSheetUri = Services.io - .newURI(vAPI.getURL("css/legacy-toolbar-button.css"), - null, null); + styleSheetUri = Services.io + .newURI(vAPI.getURL("css/legacy-toolbar-button.css"), + null, null); - // Register global so it works in all windows, including palette - if (!sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET)) { + // Register global so it works in all windows, including palette + if (!sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET)) { sss.loadAndRegisterSheet(styleSheetUri, sss.AUTHOR_SHEET); - } + } } let document = window.document; @@ -763,21 +763,21 @@ // https://github.com/gorhill/uMatrix/issues/357 // Already installed? if (document.getElementById(tbb.id) !== null) { - return; + return; } let toolbox = document.getElementById('navigator-toolbox') - || document.getElementById('mail-toolbox'); - + || document.getElementById('mail-toolbox'); + if (toolbox === null) { - return; + return; } let toolbarButton = createToolbarButton(window); let palette = toolbox.palette; if (palette && palette.querySelector('#' + tbb.id) === null) { - palette.appendChild(toolbarButton); + palette.appendChild(toolbarButton); } // Find the place to put the button. @@ -785,75 +785,75 @@ // undefined. Seen while testing popup test number 3: // http://raymondhill.net/ublock/popup.html let toolbars = toolbox.externalToolbars - ? toolbox.externalToolbars.slice() - : []; - + ? toolbox.externalToolbars.slice() + : []; + for (let child of toolbox.children) { - if (child.localName === 'toolbar') { + if (child.localName === 'toolbar') { toolbars.push(child); - } + } } for (let toolbar of toolbars) { - let currentsetString = toolbar.getAttribute('currentset'); - if (!currentsetString) { + let currentsetString = toolbar.getAttribute('currentset'); + if (!currentsetString) { continue; - } - - let currentset = currentsetString.split(/\s*,\s*/); - let index = currentset.indexOf(tbb.id); - if (index === -1) { + } + + let currentset = currentsetString.split(/\s*,\s*/); + let index = currentset.indexOf(tbb.id); + if (index === -1) { continue; - } - - // This can occur with Pale Moon: - // "TypeError: toolbar.insertItem is not a function" - if (typeof toolbar.insertItem !== 'function') { + } + + // This can occur with Pale Moon: + // "TypeError: toolbar.insertItem is not a function" + if (typeof toolbar.insertItem !== 'function') { continue; - } - - // Found our button on this toolbar - but where on it? - let before = null; - for (let i = index+1; i<currentset.length; ++i) { - // The [id=...] notation doesn't work on - // space elements as they get a random ID each session - // (or something like that) - // https://libregit.org/heckyel/ematrix/issues/5 - // https://libregit.org/heckyel/ematrix/issues/6 - - // Based on JustOff's snippet from the Pale Moon - // forum. It was reorganized because I find it - // more readable like this, but he did most of the - // work. - let space = /^(spring|spacer|separator)$/.exec(currentset[i]); - if (space !== null) { - let elems = toolbar.querySelectorAll('toolbar'+space[1]); - - let count = currentset.slice(i-currentset.length) - .filter(function (x) {return x == space[1];}) - .length; - - before = - toolbar.querySelector('[id="' - + elems[elems.length-count].id - + '"]'); - } else { - before = toolbar.querySelector('[id="'+currentset[i]+'"]'); - } + } + + // Found our button on this toolbar - but where on it? + let before = null; + for (let i = index+1; i<currentset.length; ++i) { + // The [id=...] notation doesn't work on + // space elements as they get a random ID each session + // (or something like that) + // https://libregit.org/heckyel/ematrix/issues/5 + // https://libregit.org/heckyel/ematrix/issues/6 + + // Based on JustOff's snippet from the Pale Moon + // forum. It was reorganized because I find it + // more readable like this, but he did most of the + // work. + let space = /^(spring|spacer|separator)$/.exec(currentset[i]); + if (space !== null) { + let elems = toolbar.querySelectorAll('toolbar'+space[1]); + + let count = currentset.slice(i-currentset.length) + .filter(function (x) {return x == space[1];}) + .length; + + before = + toolbar.querySelector('[id="' + + elems[elems.length-count].id + + '"]'); + } else { + before = toolbar.querySelector('[id="'+currentset[i]+'"]'); + } if ( before !== null ) { - break; + break; } - } + } - toolbar.insertItem(tbb.id, before); - break; + toolbar.insertItem(tbb.id, before); + break; } // https://github.com/gorhill/uBlock/issues/763 // We are done if our toolbar button is already installed // in one of the toolbar. if (palette !== null && toolbarButton.parentElement !== palette) { - return; + return; } // No button yet so give it a default location. If forcing @@ -862,181 +862,181 @@ // available or visible!) let navbar = document.getElementById('nav-bar'); if (navbar !== null - && !vAPI.localStorage.getBool('legacyToolbarButtonAdded')) { - // https://github.com/gorhill/uBlock/issues/264 - // Find a child customizable palette, if any. - navbar = navbar.querySelector('.customization-target') || navbar; - navbar.appendChild(toolbarButton); - navbar.setAttribute('currentset', navbar.currentSet); - document.persist(navbar.id, 'currentset'); - vAPI.localStorage.setBool('legacyToolbarButtonAdded', 'true'); + && !vAPI.localStorage.getBool('legacyToolbarButtonAdded')) { + // https://github.com/gorhill/uBlock/issues/264 + // Find a child customizable palette, if any. + navbar = navbar.querySelector('.customization-target') || navbar; + navbar.appendChild(toolbarButton); + navbar.setAttribute('currentset', navbar.currentSet); + document.persist(navbar.id, 'currentset'); + vAPI.localStorage.setBool('legacyToolbarButtonAdded', 'true'); } - }; + }; - let canAddLegacyToolbarButton = function (window) { + let canAddLegacyToolbarButton = function (window) { let document = window.document; if (!document - || document.readyState !== 'complete' - || document.getElementById('nav-bar') === null) { - return false; + || document.readyState !== 'complete' + || document.getElementById('nav-bar') === null) { + return false; } - + let toolbox = document.getElementById('navigator-toolbox') - || document.getElementById('mail-toolbox'); + || document.getElementById('mail-toolbox'); return toolbox !== null && !!toolbox.palette; - }; + }; - let onPopupCloseRequested = function ({target}) { + let onPopupCloseRequested = function ({target}) { let document = target.ownerDocument; if (!document) { - return; + return; } - + let toolbarButtonPanel = document.getElementById(tbb.viewId); if (toolbarButtonPanel === null) { - return; + return; } - + // `hidePopup` reported as not existing while testing // legacy button on FF 41.0.2. // https://bugzilla.mozilla.org/show_bug.cgi?id=1151796 if (typeof toolbarButtonPanel.hidePopup === 'function') { - toolbarButtonPanel.hidePopup(); + toolbarButtonPanel.hidePopup(); } - }; + }; - let shutdown = function () { + let shutdown = function () { for (let win of vAPI.window.getWindows()) { - let toolbarButton = win.document.getElementById(tbb.id); - if (toolbarButton) { + let toolbarButton = win.document.getElementById(tbb.id); + if (toolbarButton) { toolbarButton.parentNode.removeChild(toolbarButton); - } + } } if (styleSheetUri !== null) { - var sss = Cc["@mozilla.org/content/style-sheet-service;1"] + var sss = Cc["@mozilla.org/content/style-sheet-service;1"] .getService(Ci.nsIStyleSheetService); - if (sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET)) { + if (sss.sheetRegistered(styleSheetUri, sss.AUTHOR_SHEET)) { sss.unregisterSheet(styleSheetUri, sss.AUTHOR_SHEET); - } - styleSheetUri = null; + } + styleSheetUri = null; } vAPI.messaging.globalMessageManager - .removeMessageListener(location.host + ':closePopup', - onPopupCloseRequested); - }; + .removeMessageListener(location.host + ':closePopup', + onPopupCloseRequested); + }; - tbb.attachToNewWindow = function (win) { + tbb.attachToNewWindow = function (win) { vAPI.deferUntil(canAddLegacyToolbarButton.bind(null, win), - addLegacyToolbarButton.bind(null, win)); - }; + addLegacyToolbarButton.bind(null, win)); + }; - tbb.init = function () { + tbb.init = function () { vAPI.messaging.globalMessageManager - .addMessageListener(location.host + ':closePopup', - onPopupCloseRequested); - + .addMessageListener(location.host + ':closePopup', + onPopupCloseRequested); + vAPI.addCleanUpTask(shutdown); - }; + }; })(); (function() { - // Add toolbar button for Basilisk - if (Services.appinfo.ID !== "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") { - return; - } - - let tbb = vAPI.toolbarButton; - if (tbb.init !== null) { + // Add toolbar button for Basilisk + if (Services.appinfo.ID !== "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}") { return; - } - // if ( Services.vc.compare(Services.appinfo.version, '36.0') < 0 ) { - // return null; - // } - let CustomizableUI = null; - try { + } + + let tbb = vAPI.toolbarButton; + if (tbb.init !== null) { + return; + } + // if ( Services.vc.compare(Services.appinfo.version, '36.0') < 0 ) { + // return null; + // } + let CustomizableUI = null; + try { CustomizableUI = - Cu.import('resource:///modules/CustomizableUI.jsm', null) - .CustomizableUI; - } catch (ex) { - // Ignore - } - if (CustomizableUI === null) { + Cu.import('resource:///modules/CustomizableUI.jsm', null) + .CustomizableUI; + } catch (ex) { + // Ignore + } + if (CustomizableUI === null) { return null; - } - tbb.codePath = 'australis'; - tbb.CustomizableUI = CustomizableUI; - tbb.defaultArea = CustomizableUI.AREA_NAVBAR; + } + tbb.codePath = 'australis'; + tbb.CustomizableUI = CustomizableUI; + tbb.defaultArea = CustomizableUI.AREA_NAVBAR; - let CUIEvents = {}; + let CUIEvents = {}; - let badgeCSSRules = 'background: #000;color: #fff'; + let badgeCSSRules = 'background: #000;color: #fff'; - let updateBadgeStyle = function () { + let updateBadgeStyle = function () { for (let win of vAPI.window.getWindows()) { - let button = win.document.getElementById(tbb.id); - if (button === null) { + let button = win.document.getElementById(tbb.id); + if (button === null) { continue; - } - let badge = button.ownerDocument - .getAnonymousElementByAttribute(button, - 'class', - 'toolbarbutton-badge'); - if (!badge) { + } + let badge = button.ownerDocument + .getAnonymousElementByAttribute(button, + 'class', + 'toolbarbutton-badge'); + if (!badge) { continue; - } + } - badge.style.cssText = badgeCSSRules; + badge.style.cssText = badgeCSSRules; } - }; + }; - let updateBadge = function () { + let updateBadge = function () { let wId = tbb.id; let buttonInPanel = - CustomizableUI.getWidget(wId).areaType - === CustomizableUI.TYPE_MENU_PANEL; + CustomizableUI.getWidget(wId).areaType + === CustomizableUI.TYPE_MENU_PANEL; for (let win of vAPI.window.getWindows()) { - let button = win.document.getElementById(wId); - if (button === null) { + let button = win.document.getElementById(wId); + if (button === null) { continue; - } - - if (buttonInPanel) { + } + + if (buttonInPanel) { button.classList.remove('badged-button'); continue; - } - - button.classList.add('badged-button'); + } + + button.classList.add('badged-button'); } if (buttonInPanel) { - return; + return; } // Anonymous elements need some time to be reachable vAPI.setTimeout(updateBadgeStyle, 250); - }.bind(CUIEvents); + }.bind(CUIEvents); - CUIEvents.onCustomizeEnd = updateBadge; - CUIEvents.onWidgetAdded = updateBadge; - CUIEvents.onWidgetUnderflow = updateBadge; + CUIEvents.onCustomizeEnd = updateBadge; + CUIEvents.onWidgetAdded = updateBadge; + CUIEvents.onWidgetUnderflow = updateBadge; - let onPopupCloseRequested = function ({target}) { + let onPopupCloseRequested = function ({target}) { if (typeof tbb.closePopup === 'function') { - tbb.closePopup(target); + tbb.closePopup(target); } - }; + }; - let shutdown = function () { + let shutdown = function () { for (let win of vAPI.window.getWindows()) { - let panel = win.document.getElementById(tbb.viewId); - if (panel !== null && panel.parentNode !== null) { + let panel = win.document.getElementById(tbb.viewId); + if (panel !== null && panel.parentNode !== null) { panel.parentNode.removeChild(panel); - } - - win.QueryInterface(Ci.nsIInterfaceRequestor) + } + + win.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils) .removeSheet(styleURI, 1); } @@ -1045,13 +1045,13 @@ CustomizableUI.destroyWidget(tbb.id); vAPI.messaging.globalMessageManager - .removeMessageListener(location.host + ':closePopup', - onPopupCloseRequested); - }; + .removeMessageListener(location.host + ':closePopup', + onPopupCloseRequested); + }; - let styleURI = null; + let styleURI = null; - tbb.onBeforeCreated = function (doc) { + tbb.onBeforeCreated = function (doc) { let panel = doc.createElement('panelview'); this.populatePanel(doc, panel); @@ -1059,189 +1059,189 @@ doc.getElementById('PanelUI-multiView').appendChild(panel); doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils) - .loadSheet(styleURI, 1); - }; + .getInterface(Ci.nsIDOMWindowUtils) + .loadSheet(styleURI, 1); + }; - tbb.onCreated = function (button) { + tbb.onCreated = function (button) { button.setAttribute('badge', ''); vAPI.setTimeout(updateBadge, 250); - }; + }; - tbb.onBeforePopupReady = function () { + tbb.onBeforePopupReady = function () { // https://github.com/gorhill/uBlock/issues/83 // Add `portrait` class if width is constrained. try { - this.contentDocument.body - .classList.toggle('portrait', - CustomizableUI.getWidget(tbb.id).areaType - === CustomizableUI.TYPE_MENU_PANEL); + this.contentDocument.body + .classList.toggle('portrait', + CustomizableUI.getWidget(tbb.id).areaType + === CustomizableUI.TYPE_MENU_PANEL); } catch (ex) { - // Ignore + // Ignore } - }; + }; - tbb.closePopup = function (tabBrowser) { + tbb.closePopup = function (tabBrowser) { CustomizableUI.hidePanelForNode(tabBrowser - .ownerDocument - .getElementById(tbb.viewId)); - }; + .ownerDocument + .getElementById(tbb.viewId)); + }; - tbb.init = function () { + tbb.init = function () { vAPI.messaging.globalMessageManager - .addMessageListener(location.host + ':closePopup', - onPopupCloseRequested); + .addMessageListener(location.host + ':closePopup', + onPopupCloseRequested); CustomizableUI.addListener(CUIEvents); var style = [ - '#' + this.id + '.off {', + '#' + this.id + '.off {', 'list-style-image: url(', vAPI.getURL('img/browsericons/icon19-off.png'), ');', - '}', - '#' + this.id + ' {', + '}', + '#' + this.id + ' {', 'list-style-image: url(', vAPI.getURL('img/browsericons/icon19-19.png'), ');', - '}', - '#' + this.viewId + ', #' + this.viewId + ' > iframe {', + '}', + '#' + this.viewId + ', #' + this.viewId + ' > iframe {', 'height: 290px;', 'max-width: none !important;', 'min-width: 0 !important;', 'overflow: hidden !important;', 'padding: 0 !important;', 'width: 160px;', - '}' + '}' ]; styleURI = - Services.io.newURI('data:text/css,' - +encodeURIComponent(style.join('')), - null, - null); + Services.io.newURI('data:text/css,' + +encodeURIComponent(style.join('')), + null, + null); CustomizableUI.createWidget(this); vAPI.addCleanUpTask(shutdown); - }; + }; })(); // No toolbar button. (function () { - // Just to ensure the number of cleanup tasks is as expected: toolbar - // button code is one single cleanup task regardless of platform. - // eMatrix: might not be needed anymore - if (vAPI.toolbarButton.init === null) { + // Just to ensure the number of cleanup tasks is as expected: toolbar + // button code is one single cleanup task regardless of platform. + // eMatrix: might not be needed anymore + if (vAPI.toolbarButton.init === null) { vAPI.addCleanUpTask(function(){}); - } + } })(); if (vAPI.toolbarButton.init !== null) { - vAPI.toolbarButton.init(); + vAPI.toolbarButton.init(); } let optionsObserver = (function () { - let addonId = 'eMatrix@vannilla.org'; + let addonId = 'eMatrix@vannilla.org'; - let commandHandler = function () { + let commandHandler = function () { switch (this.id) { case 'showDashboardButton': - vAPI.tabs.open({ - url: 'dashboard.html', - index: -1, - }); - break; + vAPI.tabs.open({ + url: 'dashboard.html', + index: -1, + }); + break; case 'showLoggerButton': - vAPI.tabs.open({ - url: 'logger-ui.html', - index: -1, - }); - break; + vAPI.tabs.open({ + url: 'logger-ui.html', + index: -1, + }); + break; default: - break; + break; } - }; + }; - let setupOptionsButton = function (doc, id) { + let setupOptionsButton = function (doc, id) { let button = doc.getElementById(id); if (button === null) { - return; + return; } button.addEventListener('command', commandHandler); button.label = vAPI.i18n(id); - }; + }; - let setupOptionsButtons = function (doc) { + let setupOptionsButtons = function (doc) { setupOptionsButton(doc, 'showDashboardButton'); setupOptionsButton(doc, 'showLoggerButton'); - }; + }; - let observer = { + let observer = { observe: function (doc, topic, id) { - if (id !== addonId) { + if (id !== addonId) { return; - } + } - setupOptionsButtons(doc); + setupOptionsButtons(doc); } - }; + }; - var canInit = function() { - // https://github.com/gorhill/uBlock/issues/948 - // Older versions of Firefox can throw here when looking - // up `currentURI`. + var canInit = function() { + // https://github.com/gorhill/uBlock/issues/948 + // Older versions of Firefox can throw here when looking + // up `currentURI`. try { - let tabBrowser = vAPI.tabs.manager.currentBrowser(); - return tabBrowser - && tabBrowser.currentURI - && tabBrowser.currentURI.spec === 'about:addons' - && tabBrowser.contentDocument - && tabBrowser.contentDocument.readyState === 'complete'; + let tabBrowser = vAPI.tabs.manager.currentBrowser(); + return tabBrowser + && tabBrowser.currentURI + && tabBrowser.currentURI.spec === 'about:addons' + && tabBrowser.contentDocument + && tabBrowser.contentDocument.readyState === 'complete'; } catch (ex) { - // Ignore + // Ignore } - }; + }; - // Manually add the buttons if the `about:addons` page is - // already opened. - let init = function () { + // Manually add the buttons if the `about:addons` page is + // already opened. + let init = function () { if (canInit()) { - setupOptionsButtons(vAPI.tabs.manager - .currentBrowser().contentDocument); + setupOptionsButtons(vAPI.tabs.manager + .currentBrowser().contentDocument); } - }; + }; - let unregister = function () { + let unregister = function () { Services.obs.removeObserver(observer, 'addon-options-displayed'); - }; + }; - let register = function () { + let register = function () { Services.obs.addObserver(observer, - 'addon-options-displayed', - false); + 'addon-options-displayed', + false); vAPI.addCleanUpTask(unregister); vAPI.deferUntil(canInit, init, { next: 463 }); - }; + }; - return { + return { register: register, unregister: unregister - }; + }; })(); optionsObserver.register(); vAPI.onLoadAllCompleted = function() { - // This is called only once, when everything has been loaded - // in memory after the extension was launched. It can be used - // to inject content scripts in already opened web pages, to - // remove whatever nuisance could make it to the web pages - // before uBlock was ready. - for (let browser of vAPI.tabs.manager.browsers()) { + // This is called only once, when everything has been loaded + // in memory after the extension was launched. It can be used + // to inject content scripts in already opened web pages, to + // remove whatever nuisance could make it to the web pages + // before uBlock was ready. + for (let browser of vAPI.tabs.manager.browsers()) { browser.messageManager - .sendAsyncMessage(location.host + '-load-completed'); - } + .sendAsyncMessage(location.host + '-load-completed'); + } }; // Likelihood is that we do not have to punycode: given punycode overhead, @@ -1250,16 +1250,16 @@ var isNotASCII = /[^\x21-\x7F]/; vAPI.punycodeHostname = function (hostname) { - return isNotASCII.test(hostname) - ? punycodeHostname(hostname) - : hostname; + return isNotASCII.test(hostname) + ? punycodeHostname(hostname) + : hostname; }; vAPI.punycodeURL = function (url) { - if (isNotASCII.test(url)) { + if (isNotASCII.test(url)) { return Services.io.newURI(url, null, null).asciiSpec; - } - - return url; + } + + return url; }; })(); diff --git a/js/vapi-browser.js b/js/vapi-browser.js index 0a39830..7d4d4e2 100644 --- a/js/vapi-browser.js +++ b/js/vapi-browser.js @@ -27,54 +27,54 @@ (function () { vAPI.browser = {}; - + vAPI.browser.getTabBrowser = function (win) { - return win && win.gBrowser || null; + return win && win.gBrowser || null; }; vAPI.browser.getOwnerWindow = function (target) { - if (target.ownerDocument) { + if (target.ownerDocument) { return target.ownerDocument.defaultView; - } - - return null; + } + + return null; }; vAPI.browser.settings = { - // For now, only booleans. - originalValues: {}, + // For now, only booleans. + originalValues: {}, - rememberOriginalValue: function (path, setting) { + rememberOriginalValue: function (path, setting) { let key = path + '.' + setting; if (this.originalValues.hasOwnProperty(key)) { - return; + return; } - + let hasUserValue; let branch = Services.prefs.getBranch(path + '.'); - + try { - hasUserValue = branch.prefHasUserValue(setting); + hasUserValue = branch.prefHasUserValue(setting); } catch (ex) { - // Ignore + // Ignore } - + if (hasUserValue !== undefined) { - this.originalValues[key] = hasUserValue - ? this.getValue(path, setting) - : undefined; + this.originalValues[key] = hasUserValue + ? this.getValue(path, setting) + : undefined; } - }, - clear: function (path, setting) { + }, + clear: function (path, setting) { let key = path + '.' + setting; // Value was not overriden -- nothing to restore if (this.originalValues.hasOwnProperty(key) === false) { - return; + return; } let value = this.originalValues[key]; - + // https://github.com/gorhill/uBlock/issues/292#issuecomment-109621979 // Forget the value immediately, it may change outside of // uBlock control. @@ -82,160 +82,160 @@ // Original value was a default one if (value === undefined) { - try { + try { Services.prefs.getBranch(path + '.').clearUserPref(setting); - } catch (ex) { - // Ignore - } - return; + } catch (ex) { + // Ignore + } + return; } // Reset to original value this.setValue(path, setting, value); - }, - getValue: function (path, setting) { + }, + getValue: function (path, setting) { let branch = Services.prefs.getBranch(path + '.'); - try { - switch (branch.getPrefType(setting)) { - case branch.PREF_INT: - return branch.getIntPref(setting); - case branch.PREF_BOOL: - return branch.getBoolPref(setting); - default: - // not supported - return; - } - } catch (e) { - // Ignore - } - }, - setValue: function (path, setting, value) { - let branch = Services.prefs.getBranch(path + '.'); - - try { - switch (typeof value) { - case 'number': - return branch.setIntPref(setting, value); - case 'boolean': - return branch.setBoolPref(setting, value); - default: - // not supported - return; - } - } catch (e) { - // Ignore - } - }, - setSetting: function (setting, value) { + try { + switch (branch.getPrefType(setting)) { + case branch.PREF_INT: + return branch.getIntPref(setting); + case branch.PREF_BOOL: + return branch.getBoolPref(setting); + default: + // not supported + return; + } + } catch (e) { + // Ignore + } + }, + setValue: function (path, setting, value) { + let branch = Services.prefs.getBranch(path + '.'); + + try { + switch (typeof value) { + case 'number': + return branch.setIntPref(setting, value); + case 'boolean': + return branch.setBoolPref(setting, value); + default: + // not supported + return; + } + } catch (e) { + // Ignore + } + }, + setSetting: function (setting, value) { switch (setting) { case 'prefetching': - this.rememberOriginalValue('network', 'prefetch-next'); - // https://bugzilla.mozilla.org/show_bug.cgi?id=814169 - // Sigh. - // eMatrix: doesn't seem the case for Pale - // Moon/Basilisk, but let's keep this anyway - this.rememberOriginalValue('network.http', 'speculative-parallel-limit'); - - // https://github.com/gorhill/uBlock/issues/292 - // "true" means "do not disable", i.e. leave entry alone - if (value) { + this.rememberOriginalValue('network', 'prefetch-next'); + // https://bugzilla.mozilla.org/show_bug.cgi?id=814169 + // Sigh. + // eMatrix: doesn't seem the case for Pale + // Moon/Basilisk, but let's keep this anyway + this.rememberOriginalValue('network.http', 'speculative-parallel-limit'); + + // https://github.com/gorhill/uBlock/issues/292 + // "true" means "do not disable", i.e. leave entry alone + if (value) { this.clear('network', 'prefetch-next'); this.clear('network.http', 'speculative-parallel-limit'); - } else { + } else { this.setValue('network', 'prefetch-next', false); this.setValue('network.http', - 'speculative-parallel-limit', 0); - } - break; + 'speculative-parallel-limit', 0); + } + break; case 'hyperlinkAuditing': - this.rememberOriginalValue('browser', 'send_pings'); - this.rememberOriginalValue('beacon', 'enabled'); - - // https://github.com/gorhill/uBlock/issues/292 - // "true" means "do not disable", i.e. leave entry alone - if (value) { + this.rememberOriginalValue('browser', 'send_pings'); + this.rememberOriginalValue('beacon', 'enabled'); + + // https://github.com/gorhill/uBlock/issues/292 + // "true" means "do not disable", i.e. leave entry alone + if (value) { this.clear('browser', 'send_pings'); this.clear('beacon', 'enabled'); - } else { + } else { this.setValue('browser', 'send_pings', false); this.setValue('beacon', 'enabled', false); - } - break; + } + break; case 'webrtcIPAddress': - let prefName; - let prefVal; - - // https://github.com/gorhill/uBlock/issues/894 - // Do not disable completely WebRTC if it can be avoided. FF42+ - // has a `media.peerconnection.ice.default_address_only` pref which - // purpose is to prevent local IP address leakage. - if (this.getValue('media.peerconnection', - 'ice.default_address_only') !== undefined) { + let prefName; + let prefVal; + + // https://github.com/gorhill/uBlock/issues/894 + // Do not disable completely WebRTC if it can be avoided. FF42+ + // has a `media.peerconnection.ice.default_address_only` pref which + // purpose is to prevent local IP address leakage. + if (this.getValue('media.peerconnection', + 'ice.default_address_only') !== undefined) { prefName = 'ice.default_address_only'; prefVal = true; - } else { + } else { prefName = 'enabled'; prefVal = false; - } + } - this.rememberOriginalValue('media.peerconnection', prefName); - if (value) { + this.rememberOriginalValue('media.peerconnection', prefName); + if (value) { this.clear('media.peerconnection', prefName); - } else { + } else { this.setValue('media.peerconnection', prefName, prefVal); - } - break; + } + break; default: - break; + break; } - }, - set: function (details) { + }, + set: function (details) { for (let setting in details) { - if (details.hasOwnProperty(setting) === false) { + if (details.hasOwnProperty(setting) === false) { continue; - } - this.setSetting(setting, !!details[setting]); + } + this.setSetting(setting, !!details[setting]); } - }, - restoreAll: function () { + }, + restoreAll: function () { let pos; for (let key in this.originalValues) { - if (this.originalValues.hasOwnProperty(key) === false) { + if (this.originalValues.hasOwnProperty(key) === false) { continue; - } - - pos = key.lastIndexOf('.'); - this.clear(key.slice(0, pos), key.slice(pos + 1)); + } + + pos = key.lastIndexOf('.'); + this.clear(key.slice(0, pos), key.slice(pos + 1)); } - }, + }, }; vAPI.addCleanUpTask(vAPI.browser.settings - .restoreAll.bind(vAPI.browser.settings)); + .restoreAll.bind(vAPI.browser.settings)); vAPI.browser.data = {}; vAPI.browser.data.clearCache = function (callback) { - // PURGE_DISK_DATA_ONLY:1 - // PURGE_DISK_ALL:2 - // PURGE_EVERYTHING:3 - // However I verified that no argument does clear the cache data. - // There is no cache2 for older versions of Firefox. - if (Services.cache2) { + // PURGE_DISK_DATA_ONLY:1 + // PURGE_DISK_ALL:2 + // PURGE_EVERYTHING:3 + // However I verified that no argument does clear the cache data. + // There is no cache2 for older versions of Firefox. + if (Services.cache2) { Services.cache2.clear(); - } else if (Services.cache) { + } else if (Services.cache) { Services.cache.evictEntries(Services.cache.STORE_ON_DISK); - } - - if (typeof callback === 'function') { + } + + if (typeof callback === 'function') { callback(); - } + } }; vAPI.browser.data.clearOrigin = function(/* domain */) { - // TODO - // eMatrix: is this actually needed? I don't really know what - // it's supposed to do anyway. + // TODO + // eMatrix: is this actually needed? I don't really know what + // it's supposed to do anyway. }; })(); diff --git a/js/vapi-client.js b/js/vapi-client.js index 81ddd51..4c48d03 100644 --- a/js/vapi-client.js +++ b/js/vapi-client.js @@ -27,155 +27,155 @@ (function (self) { vAPI.sessionId = String.fromCharCode(Date.now() % 25 + 97) + - Math.random().toString(36).slice(2); + Math.random().toString(36).slice(2); vAPI.shutdown = (function () { - let jobs = []; + let jobs = []; - let add = function (job) { + let add = function (job) { jobs.push(job); - }; + }; - let exec = function () { + let exec = function () { //console.debug('Shutting down...'); let job; while ((job = jobs.pop())) { - job(); + job(); } - }; + }; - return { + return { add: add, exec: exec - }; + }; })(); vAPI.messaging = { - listeners: new Set(), - pending: new Map(), - requestId: 1, - connected: false, - messageListenerCallback: null, - toggleListenerCallback: null, - - start: function () { + listeners: new Set(), + pending: new Map(), + requestId: 1, + connected: false, + messageListenerCallback: null, + toggleListenerCallback: null, + + start: function () { this.addListener(this.builtinListener); if (this.toggleListenerCallback === null) { - this.toggleListenerCallback = this.toggleListener.bind(this); + this.toggleListenerCallback = this.toggleListener.bind(this); } - + window.addEventListener('pagehide', - this.toggleListenerCallback, true); + this.toggleListenerCallback, true); window.addEventListener('pageshow', - this.toggleListenerCallback, true); - }, - shutdown: function () { + this.toggleListenerCallback, true); + }, + shutdown: function () { if (this.toggleListenerCallback !== null) { - window.removeEventListener('pagehide', - this.toggleListenerCallback, true); - window.removeEventListener('pageshow', - this.toggleListenerCallback, true); + window.removeEventListener('pagehide', + this.toggleListenerCallback, true); + window.removeEventListener('pageshow', + this.toggleListenerCallback, true); } this.removeAllListeners(); - + //service pending callbacks var pending = this.pending; this.pending.clear(); for (let callback of pending.values()) { - if (typeof callback === 'function') { + if (typeof callback === 'function') { callback(null); - } + } } - }, - connect: function () { + }, + connect: function () { if (!this.connected) { - if (this.messageListenerCallback === null) { + if (this.messageListenerCallback === null) { this.messageListenerCallback = - this.messageListener.bind(this); - } - addMessageListener(this.messageListenerCallback); - this.connected = true; + this.messageListener.bind(this); + } + addMessageListener(this.messageListenerCallback); + this.connected = true; } - }, - disconnect: function () { + }, + disconnect: function () { if (this.connected) { - removeMessageListener(); - this.connected = false; + removeMessageListener(); + this.connected = false; } - }, - messageListener: function (msg) { + }, + messageListener: function (msg) { let details = JSON.parse(msg); if (!details) { - return; + return; } if (details.broadcast) { - this.sendToListeners(details.msg); - return; + this.sendToListeners(details.msg); + return; } if (details.requestId) { - let listener = this.pending.get(details.requestId); - if (listener !== undefined) { + let listener = this.pending.get(details.requestId); + if (listener !== undefined) { this.pending.delete(details.requestId); listener(details.msg); return; - } + } } - }, - builtinListener: function (msg) { + }, + builtinListener: function (msg) { if (typeof msg.cmd === 'string' && msg.cmd === 'injectScript') { - let details = msg.details; - if (!details.allFrames && window !== window.top) { + let details = msg.details; + if (!details.allFrames && window !== window.top) { return; - } - self.injectScript(details.file); + } + self.injectScript(details.file); } - }, - send: function (channelName, message, callback) { + }, + send: function (channelName, message, callback) { this.connect() message = { - channelName: self._sandboxId_ + '|' + channelName, - msg: message + channelName: self._sandboxId_ + '|' + channelName, + msg: message }; if (callback) { - message.requestId = this.requestId++; - this.pending.set(message.requestId, callback); + message.requestId = this.requestId++; + this.pending.set(message.requestId, callback); } sendAsyncMessage('ematrix:background', message); - }, - toggleListener: function ({type, persisted}) { + }, + toggleListener: function ({type, persisted}) { if (type === 'pagehide' && !persisted) { - vAPI.shutdown.exec(); - this.shutdown(); - return; + vAPI.shutdown.exec(); + this.shutdown(); + return; } if (type === 'pagehide') { - this.disconnect(); + this.disconnect(); } else { - this.connect(); + this.connect(); } - }, - sendToListeners: function (msg) { + }, + sendToListeners: function (msg) { for (let listener of this.listeners) { - listener(msg); + listener(msg); } - }, - addListener: function (listener) { + }, + addListener: function (listener) { this.listeners.add(listener); this.connect() - }, - removeListener: function (listener) { + }, + removeListener: function (listener) { this.listeners.delete(listener); - }, - removeAllListeners: function () { + }, + removeAllListeners: function () { this.disconnect(); this.listeners.clear(); - } + } }; vAPI.messaging.start() @@ -185,8 +185,8 @@ // be injected in top window). // Needs more investigating // if ( window !== window.top ) { - // vAPI.shutdown.add(function() { - // vAPI = null; - // }); + // vAPI.shutdown.add(function() { + // vAPI = null; + // }); // } })(this); diff --git a/js/vapi-cloud.js b/js/vapi-cloud.js index cedcfcd..e3cb56b 100644 --- a/js/vapi-cloud.js +++ b/js/vapi-cloud.js @@ -27,130 +27,130 @@ (function () { vAPI.cloud = (function () { - let extensionBranchPath = 'extensions.' + location.host; - let cloudBranchPath = extensionBranchPath + '.cloudStorage'; - - // https://github.com/gorhill/uBlock/issues/80#issuecomment-132081658 - // We must use get/setComplexValue in order to properly handle strings - // with unicode characters. - let iss = Ci.nsISupportsString; - let argstr = Components.classes['@mozilla.org/supports-string;1'] + let extensionBranchPath = 'extensions.' + location.host; + let cloudBranchPath = extensionBranchPath + '.cloudStorage'; + + // https://github.com/gorhill/uBlock/issues/80#issuecomment-132081658 + // We must use get/setComplexValue in order to properly handle strings + // with unicode characters. + let iss = Ci.nsISupportsString; + let argstr = Components.classes['@mozilla.org/supports-string;1'] .createInstance(iss); - let options = { + let options = { defaultDeviceName: '', deviceName: '' - }; + }; - // User-supplied device name. - try { + // User-supplied device name. + try { options.deviceName = Services.prefs .getBranch(extensionBranchPath + '.') .getComplexValue('deviceName', iss) .data; - } catch(ex) { - // Ignore - } + } catch(ex) { + // Ignore + } - var getDefaultDeviceName = function() { + var getDefaultDeviceName = function() { var name = ''; try { - name = Services.prefs + name = Services.prefs .getBranch('services.sync.client.') .getComplexValue('name', iss) .data; } catch(ex) { - // Ignore + // Ignore } return name || window.navigator.platform || window.navigator.oscpu; - }; + }; - let start = function (dataKeys) { + let start = function (dataKeys) { let extensionBranch = - Services.prefs.getBranch(extensionBranchPath + '.'); + Services.prefs.getBranch(extensionBranchPath + '.'); let syncBranch = - Services.prefs.getBranch('services.sync.prefs.sync.'); + Services.prefs.getBranch('services.sync.prefs.sync.'); // Mark config entries as syncable argstr.data = ''; let dataKey; for (let i=0; i<dataKeys.length; ++i) { - dataKey = dataKeys[i]; - if (extensionBranch.prefHasUserValue('cloudStorage.' + dataKey) - === false) { + dataKey = dataKeys[i]; + if (extensionBranch.prefHasUserValue('cloudStorage.' + dataKey) + === false) { extensionBranch.setComplexValue('cloudStorage.' + dataKey, - iss, argstr); - } - - syncBranch.setBoolPref(cloudBranchPath + '.' + dataKey, true); + iss, argstr); + } + + syncBranch.setBoolPref(cloudBranchPath + '.' + dataKey, true); } - }; + }; - let push = function (datakey, data, callback) { + let push = function (datakey, data, callback) { let branch = Services.prefs.getBranch(cloudBranchPath + '.'); let bin = { - 'source': options.deviceName || getDefaultDeviceName(), - 'tstamp': Date.now(), - 'data': data, - 'size': 0 + 'source': options.deviceName || getDefaultDeviceName(), + 'tstamp': Date.now(), + 'data': data, + 'size': 0 }; bin.size = JSON.stringify(bin).length; argstr.data = JSON.stringify(bin); branch.setComplexValue(datakey, iss, argstr); - + if (typeof callback === 'function') { - callback(); + callback(); } - }; + }; - let pull = function (datakey, callback) { + let pull = function (datakey, callback) { let result = null; let branch = Services.prefs.getBranch(cloudBranchPath + '.'); - + try { - let json = branch.getComplexValue(datakey, iss).data; - if (typeof json === 'string') { + let json = branch.getComplexValue(datakey, iss).data; + if (typeof json === 'string') { result = JSON.parse(json); - } + } } catch(ex) { - // Ignore + // Ignore } - + callback(result); - }; + }; - let getOptions = function (callback) { + let getOptions = function (callback) { if (typeof callback !== 'function') { - return; + return; } - + options.defaultDeviceName = getDefaultDeviceName(); callback(options); - }; + }; - let setOptions = function (details, callback) { + let setOptions = function (details, callback) { if (typeof details !== 'object' || details === null) { - return; + return; } let branch = Services.prefs.getBranch(extensionBranchPath + '.'); if (typeof details.deviceName === 'string') { - argstr.data = details.deviceName; - branch.setComplexValue('deviceName', iss, argstr); - options.deviceName = details.deviceName; + argstr.data = details.deviceName; + branch.setComplexValue('deviceName', iss, argstr); + options.deviceName = details.deviceName; } getOptions(callback); - }; + }; - return { + return { start: start, push: push, pull: pull, getOptions: getOptions, setOptions: setOptions - }; + }; })(); })(); diff --git a/js/vapi-common.js b/js/vapi-common.js index c95d01f..6a79096 100644 --- a/js/vapi-common.js +++ b/js/vapi-common.js @@ -32,82 +32,82 @@ Cu.import('resource://gre/modules/Services.jsm'); (function (self) { vAPI.setTimeout = vAPI.setTimeout || function (callback, delay, extra) { - return setTimeout(function (a) { - callback(a); - }, delay, extra); + return setTimeout(function (a) { + callback(a); + }, delay, extra); }; // http://www.w3.org/International/questions/qa-scripts#directions let setScriptDirection = function(language) { - let dir = - ['ar', 'he', 'fa', 'ps', 'ur'].indexOf(language) !== -1 - ? 'rtl' - : 'ltr'; - - document.body.setAttribute('dir', dir); + let dir = + ['ar', 'he', 'fa', 'ps', 'ur'].indexOf(language) !== -1 + ? 'rtl' + : 'ltr'; + + document.body.setAttribute('dir', dir); }; vAPI.download = function (details) { - if (!details.url) { + if (!details.url) { return; - } + } - let a = document.createElement('a'); - a.href = details.url; - a.setAttribute('download', details.filename || ''); - a.dispatchEvent(new MouseEvent('click')); + let a = document.createElement('a'); + a.href = details.url; + a.setAttribute('download', details.filename || ''); + a.dispatchEvent(new MouseEvent('click')); }; vAPI.insertHTML = (function () { - const parser = Cc['@mozilla.org/parserutils;1'] + const parser = Cc['@mozilla.org/parserutils;1'] .getService(Ci.nsIParserUtils); - // https://github.com/gorhill/uBlock/issues/845 - // Apparently dashboard pages execute with `about:blank` principal. + // https://github.com/gorhill/uBlock/issues/845 + // Apparently dashboard pages execute with `about:blank` principal. - return function (node, html) { + return function (node, html) { while (node.firstChild) { - node.removeChild(node.firstChild); + node.removeChild(node.firstChild); } - let parsed = - parser.parseFragment(html, - parser.SanitizerAllowStyle, - false, - Services.io.newURI('about:blank', - null, null), - document.documentElement); + let parsed = + parser.parseFragment(html, + parser.SanitizerAllowStyle, + false, + Services.io.newURI('about:blank', + null, null), + document.documentElement); node.appendChild(parsed); - }; + }; })(); vAPI.getURL = function (path) { - return 'chrome://' - + location.host - + '/content/' - + path.replace(/^\/+/, ''); + return 'chrome://' + + location.host + + '/content/' + + path.replace(/^\/+/, ''); }; - + vAPI.i18n = (function () { - let stringBundle = - Services.strings.createBundle('chrome://' - + location.host - + '/locale/messages.properties'); + let stringBundle = + Services.strings.createBundle('chrome://' + + location.host + + '/locale/messages.properties'); - return function (s) { + return function (s) { try { - return stringBundle.GetStringFromName(s); + return stringBundle.GetStringFromName(s); } catch (ex) { - return ''; + return ''; } - }; + }; })(); setScriptDirection(navigator.language); vAPI.closePopup = function() { - sendAsyncMessage(location.host + ':closePopup'); + sendAsyncMessage(location.host + ':closePopup'); }; // A localStorage-like object which should be accessible from the @@ -115,50 +115,50 @@ Cu.import('resource://gre/modules/Services.jsm'); // This storage is optional, but it is nice to have, for a more polished user // experience. vAPI.localStorage = { - pbName: '', - pb: null, - str: Cc['@mozilla.org/supports-string;1'] + pbName: '', + pb: null, + str: Cc['@mozilla.org/supports-string;1'] .createInstance(Ci.nsISupportsString), - - init: function (pbName) { + + init: function (pbName) { this.pbName = pbName; this.pb = Services.prefs.getBranch(pbName); - }, - getItem: function (key) { + }, + getItem: function (key) { try { - return this.pb - .getComplexValue(key, - Ci.nsISupportsString).data; + return this.pb + .getComplexValue(key, + Ci.nsISupportsString).data; } catch (ex) { - return null; + return null; } - }, - setItem: function (key, value) { + }, + setItem: function (key, value) { this.str.data = value; this.pb.setComplexValue(key, - Ci.nsISupportsString, - this.str); - }, - getBool: function (key) { + Ci.nsISupportsString, + this.str); + }, + getBool: function (key) { try { - return this.pb.getBoolPref(key); + return this.pb.getBoolPref(key); } catch (ex) { - return null; + return null; } - }, - setBool: function (key, value) { + }, + setBool: function (key, value) { this.pb.setBoolPref(key, value); - }, - setDefaultBool: function (key, defaultValue) { + }, + setDefaultBool: function (key, defaultValue) { Services.prefs.getDefaultBranch(this.pbName) - .setBoolPref(key, defaultValue); - }, - removeItem: function (key) { + .setBoolPref(key, defaultValue); + }, + removeItem: function (key) { this.pb.clearUserPref(key); - }, - clear: function () { + }, + clear: function () { this.pb.deleteBranch(''); - } + } }; vAPI.localStorage.init('extensions.' + location.host + '.'); diff --git a/js/vapi-contextmenu.js b/js/vapi-contextmenu.js index f36d8ba..7cf8568 100644 --- a/js/vapi-contextmenu.js +++ b/js/vapi-contextmenu.js @@ -27,186 +27,186 @@ (function () { vAPI.contextMenu = { - contextMap: { + contextMap: { frame: 'inFrame', link: 'onLink', image: 'onImage', audio: 'onAudio', video: 'onVideo', editable: 'onEditableArea' - } + } }; vAPI.contextMenu.displayMenuItem = function ({target}) { - let doc = target.ownerDocument; - let gContextMenu = doc.defaultView.gContextMenu; - if (!gContextMenu.browser) { + let doc = target.ownerDocument; + let gContextMenu = doc.defaultView.gContextMenu; + if (!gContextMenu.browser) { return; - } + } - let menuitem = doc.getElementById(vAPI.contextMenu.menuItemId); - let currentURI = gContextMenu.browser.currentURI; + let menuitem = doc.getElementById(vAPI.contextMenu.menuItemId); + let currentURI = gContextMenu.browser.currentURI; - // https://github.com/chrisaljoudi/uBlock/issues/105 - // TODO: Should the element picker works on any kind of pages? - if (!currentURI.schemeIs('http') && !currentURI.schemeIs('https')) { + // https://github.com/chrisaljoudi/uBlock/issues/105 + // TODO: Should the element picker works on any kind of pages? + if (!currentURI.schemeIs('http') && !currentURI.schemeIs('https')) { menuitem.setAttribute('hidden', true); return; - } + } - let ctx = vAPI.contextMenu.contexts; + let ctx = vAPI.contextMenu.contexts; - if (!ctx) { + if (!ctx) { menuitem.setAttribute('hidden', false); return; - } + } - let ctxMap = vAPI.contextMenu.contextMap; + let ctxMap = vAPI.contextMenu.contextMap; - for (let context of ctx) { + for (let context of ctx) { if (context === 'page' - && !gContextMenu.onLink - && !gContextMenu.onImage - && !gContextMenu.onEditableArea - && !gContextMenu.inFrame - && !gContextMenu.onVideo - && !gContextMenu.onAudio) { - menuitem.setAttribute('hidden', false); - return; + && !gContextMenu.onLink + && !gContextMenu.onImage + && !gContextMenu.onEditableArea + && !gContextMenu.inFrame + && !gContextMenu.onVideo + && !gContextMenu.onAudio) { + menuitem.setAttribute('hidden', false); + return; } if (ctxMap.hasOwnProperty(context) - && gContextMenu[ctxMap[context]]) { - menuitem.setAttribute('hidden', false); - return; + && gContextMenu[ctxMap[context]]) { + menuitem.setAttribute('hidden', false); + return; } - } + } - menuitem.setAttribute('hidden', true); + menuitem.setAttribute('hidden', true); }; vAPI.contextMenu.register = (function () { - let register = function (doc) { + let register = function (doc) { if (!this.menuItemId) { - return; + return; } // Already installed? if (doc.getElementById(this.menuItemId) !== null) { - return; + return; } let contextMenu = doc.getElementById('contentAreaContextMenu'); - + let menuitem = doc.createElement('menuitem'); menuitem.setAttribute('id', this.menuItemId); menuitem.setAttribute('label', this.menuLabel); menuitem.setAttribute('image', vAPI.getURL('img/browsericons/icon19-19.png')); menuitem.setAttribute('class', 'menuitem-iconic'); menuitem.addEventListener('command', this.onCommand); - + contextMenu.addEventListener('popupshowing', this.displayMenuItem); contextMenu.insertBefore(menuitem, doc.getElementById('inspect-separator')); - }; - - let registerSafely = function (doc, tryCount) { - // https://github.com/gorhill/uBlock/issues/906 - // Be sure document.readyState is 'complete': it could happen - // at launch time that we are called by - // vAPI.contextMenu.create() directly before the environment - // is properly initialized. + }; + + let registerSafely = function (doc, tryCount) { + // https://github.com/gorhill/uBlock/issues/906 + // Be sure document.readyState is 'complete': it could happen + // at launch time that we are called by + // vAPI.contextMenu.create() directly before the environment + // is properly initialized. if (doc.readyState === 'complete') { - register.call(this, doc); - return; + register.call(this, doc); + return; } - + if (typeof tryCount !== 'number') { - tryCount = 0; + tryCount = 0; } - + tryCount += 1; if (tryCount < 8) { - vAPI.setTimeout(registerSafely.bind(this, doc, tryCount), 200); + vAPI.setTimeout(registerSafely.bind(this, doc, tryCount), 200); } - }; + }; - return registerSafely; + return registerSafely; })(); vAPI.contextMenu.unregister = function (doc) { - if (!this.menuItemId) { + if (!this.menuItemId) { return; - } + } - let menuitem = doc.getElementById(this.menuItemId); - if (menuitem === null) { + let menuitem = doc.getElementById(this.menuItemId); + if (menuitem === null) { return; - } - - let contextMenu = menuitem.parentNode; - menuitem.removeEventListener('command', this.onCommand); - contextMenu.removeEventListener('popupshowing', this.displayMenuItem); - contextMenu.removeChild(menuitem); + } + + let contextMenu = menuitem.parentNode; + menuitem.removeEventListener('command', this.onCommand); + contextMenu.removeEventListener('popupshowing', this.displayMenuItem); + contextMenu.removeChild(menuitem); }; vAPI.contextMenu.create = function (details, callback) { - this.menuItemId = details.id; - this.menuLabel = details.title; - this.contexts = details.contexts; + this.menuItemId = details.id; + this.menuLabel = details.title; + this.contexts = details.contexts; - if (Array.isArray(this.contexts) && this.contexts.length) { + if (Array.isArray(this.contexts) && this.contexts.length) { this.contexts = this.contexts.indexOf('all') === -1 - ? this.contexts - : null; - } else { + ? this.contexts + : null; + } else { // default in Chrome this.contexts = ['page']; - } + } - this.onCommand = function () { + this.onCommand = function () { let gContextMenu = vAPI.browser.getOwnerWindow(this).gContextMenu; let details = { - menuItemId: this.id + menuItemId: this.id }; if (gContextMenu.inFrame) { - details.tagName = 'iframe'; - // Probably won't work with e10s - // eMatrix: doesn't matter ;) - details.frameUrl = gContextMenu.focusedWindow.location.href; + details.tagName = 'iframe'; + // Probably won't work with e10s + // eMatrix: doesn't matter ;) + details.frameUrl = gContextMenu.focusedWindow.location.href; } else if (gContextMenu.onImage) { - details.tagName = 'img'; - details.srcUrl = gContextMenu.mediaURL; + details.tagName = 'img'; + details.srcUrl = gContextMenu.mediaURL; } else if (gContextMenu.onAudio) { - details.tagName = 'audio'; - details.srcUrl = gContextMenu.mediaURL; + details.tagName = 'audio'; + details.srcUrl = gContextMenu.mediaURL; } else if (gContextMenu.onVideo) { - details.tagName = 'video'; - details.srcUrl = gContextMenu.mediaURL; + details.tagName = 'video'; + details.srcUrl = gContextMenu.mediaURL; } else if (gContextMenu.onLink) { - details.tagName = 'a'; - details.linkUrl = gContextMenu.linkURL; + details.tagName = 'a'; + details.linkUrl = gContextMenu.linkURL; } callback(details, { - id: vAPI.tabs.manager.tabIdFromTarget(gContextMenu.browser), - url: gContextMenu.browser.currentURI.asciiSpec + id: vAPI.tabs.manager.tabIdFromTarget(gContextMenu.browser), + url: gContextMenu.browser.currentURI.asciiSpec }); - }; + }; - for (let win of vAPI.window.getWindows()) { + for (let win of vAPI.window.getWindows()) { this.register(win.document); - } + } }; vAPI.contextMenu.remove = function () { - for (let win of vAPI.window.getWindows()) { + for (let win of vAPI.window.getWindows()) { this.unregister(win.document); - } + } - this.menuItemId = null; - this.menuLabel = null; - this.contexts = null; - this.onCommand = null; + this.menuItemId = null; + this.menuLabel = null; + this.contexts = null; + this.onCommand = null; }; })(); diff --git a/js/vapi-cookies.js b/js/vapi-cookies.js index 569205c..b1e2fb3 100644 --- a/js/vapi-cookies.js +++ b/js/vapi-cookies.js @@ -29,90 +29,90 @@ vAPI.cookies = {}; vAPI.cookies.CookieEntry = function (ffCookie) { - this.domain = ffCookie.host; - this.name = ffCookie.name; - this.path = ffCookie.path; - this.secure = ffCookie.isSecure === true; - this.session = ffCookie.expires === 0; - this.value = ffCookie.value; + this.domain = ffCookie.host; + this.name = ffCookie.name; + this.path = ffCookie.path; + this.secure = ffCookie.isSecure === true; + this.session = ffCookie.expires === 0; + this.value = ffCookie.value; }; vAPI.cookies.start = function () { - Services.obs.addObserver(this, 'cookie-changed', false); - Services.obs.addObserver(this, 'private-cookie-changed', false); - vAPI.addCleanUpTask(this.stop.bind(this)); + Services.obs.addObserver(this, 'cookie-changed', false); + Services.obs.addObserver(this, 'private-cookie-changed', false); + vAPI.addCleanUpTask(this.stop.bind(this)); }; vAPI.cookies.stop = function () { - Services.obs.removeObserver(this, 'cookie-changed'); - Services.obs.removeObserver(this, 'private-cookie-changed'); + Services.obs.removeObserver(this, 'cookie-changed'); + Services.obs.removeObserver(this, 'private-cookie-changed'); }; vAPI.cookies.observe = function (subject, topic, reason) { - //if ( topic !== 'cookie-changed' && topic !== 'private-cookie-changed' ) { - // return; - //} - // - if (reason === 'cleared' && typeof this.onAllRemoved === 'function') { + //if ( topic !== 'cookie-changed' && topic !== 'private-cookie-changed' ) { + // return; + //} + // + if (reason === 'cleared' && typeof this.onAllRemoved === 'function') { this.onAllRemoved(); return; - } - if (subject === null) { + } + if (subject === null) { return; - } - if (subject instanceof Ci.nsICookie2 === false) { + } + if (subject instanceof Ci.nsICookie2 === false) { try { - subject = subject.QueryInterface(Ci.nsICookie2); + subject = subject.QueryInterface(Ci.nsICookie2); } catch (ex) { - return; + return; } - } - if (reason === 'deleted') { + } + if (reason === 'deleted') { if (typeof this.onRemoved === 'function') { - this.onRemoved(new this.CookieEntry(subject)); + this.onRemoved(new this.CookieEntry(subject)); } return; - } - if (typeof this.onChanged === 'function') { + } + if (typeof this.onChanged === 'function') { this.onChanged(new this.CookieEntry(subject)); - } + } }; vAPI.cookies.getAll = function(callback) { - // Meant and expected to be asynchronous. - if (typeof callback !== 'function') { + // Meant and expected to be asynchronous. + if (typeof callback !== 'function') { return; - } - - let onAsync = function () { + } + + let onAsync = function () { let out = []; let enumerator = Services.cookies.enumerator; let ffcookie; while (enumerator.hasMoreElements()) { - ffcookie = enumerator.getNext(); - if (ffcookie instanceof Ci.nsICookie) { + ffcookie = enumerator.getNext(); + if (ffcookie instanceof Ci.nsICookie) { out.push(new this.CookieEntry(ffcookie)); - } + } } - + callback(out); - }; - - vAPI.setTimeout(onAsync.bind(this), 0); + }; + + vAPI.setTimeout(onAsync.bind(this), 0); }; vAPI.cookies.remove = function (details, callback) { - let uri = Services.io.newURI(details.url, null, null); - let cookies = Services.cookies; - cookies.remove(uri.asciiHost, details.name, uri.path, false, {}); - cookies.remove( '.' + uri.asciiHost, details.name, uri.path, false, {}); - - if (typeof callback === 'function') { + let uri = Services.io.newURI(details.url, null, null); + let cookies = Services.cookies; + cookies.remove(uri.asciiHost, details.name, uri.path, false, {}); + cookies.remove( '.' + uri.asciiHost, details.name, uri.path, false, {}); + + if (typeof callback === 'function') { callback({ - domain: uri.asciiHost, - name: details.name, - path: uri.path + domain: uri.asciiHost, + name: details.name, + path: uri.path }); - } + } }; })(); diff --git a/js/vapi-core.js b/js/vapi-core.js index c2c508d..94ae599 100644 --- a/js/vapi-core.js +++ b/js/vapi-core.js @@ -27,26 +27,26 @@ (function (self) { vAPI.modernFirefox = - Services.appinfo.ID === '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}' - && Services.vc.compare(Services.appinfo.version, '44') > 0; + Services.appinfo.ID === '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}' + && Services.vc.compare(Services.appinfo.version, '44') > 0; vAPI.app = { - name: 'eMatrix', - version: location.hash.slice(1), - - start: function () { - return; - }, - stop: function () { - return; - }, - restart: function () { - Cc['@mozilla.org/childprocessmessagemanager;1'] - .getService(Ci.nsIMessageSender) - .sendAsyncMessage(location.host + '-restart'); - }, + name: 'eMatrix', + version: location.hash.slice(1), + + start: function () { + return; + }, + stop: function () { + return; + }, + restart: function () { + Cc['@mozilla.org/childprocessmessagemanager;1'] + .getService(Ci.nsIMessageSender) + .sendAsyncMessage(location.host + '-restart'); + }, }; - + // List of things that needs to be destroyed when disabling the extension // Only functions should be added to it // eMatrix: taken care by vAPI.addCleanUpTask --- use that function @@ -57,75 +57,75 @@ let expectedNumberOfCleanups = 7; vAPI.addCleanUpTask = function (task) { - if (typeof task !== 'function') { - return; - } + if (typeof task !== 'function') { + return; + } - cleanupTasks.push(task); + cleanupTasks.push(task); }; vAPI.deferUntil = function (testFn, mainFn, details) { - let dtls = (typeof details !== 'object') ? {} : details; - let now = 0; - let next = dtls.next || 200; - let until = dtls.until || 2000; + let dtls = (typeof details !== 'object') ? {} : details; + let now = 0; + let next = dtls.next || 200; + let until = dtls.until || 2000; - let check = function () { + let check = function () { if (testFn() === true || now >= until) { - mainFn(); - return; + mainFn(); + return; } now += next; vAPI.setTimeout(check, next); - }; + }; - if ('sync' in dtls && dtls.sync === true) { + if ('sync' in dtls && dtls.sync === true) { check(); - } else { + } else { vAPI.setTimeout(check, 1); - } + } }; window.addEventListener('unload', function () { - // if (typeof vAPI.app.onShutdown === 'function') { + // if (typeof vAPI.app.onShutdown === 'function') { // vAPI.app.onShutdown(); - // } - - // IMPORTANT: cleanup tasks must be executed using LIFO order. - for (let i=cleanupTasks.length-1; i>=0; --i) { - try { - cleanupTasks[i](); - } catch (e) { - // Just in case a clean up task ends up throwing for - // no reason - console.error(e); - } - } - - // eMatrix: temporarily disabled - // if (cleanupTasks.length < expectedNumberOfCleanups) { + // } + + // IMPORTANT: cleanup tasks must be executed using LIFO order. + for (let i=cleanupTasks.length-1; i>=0; --i) { + try { + cleanupTasks[i](); + } catch (e) { + // Just in case a clean up task ends up throwing for + // no reason + console.error(e); + } + } + + // eMatrix: temporarily disabled + // if (cleanupTasks.length < expectedNumberOfCleanups) { // console.error - // ('eMatrix> Cleanup tasks performed: %s (out of %s)', + // ('eMatrix> Cleanup tasks performed: %s (out of %s)', // cleanupTasks.length, // expectedNumberOfCleanups); - // } - - // frameModule needs to be cleared too - let frameModuleURL = vAPI.getURL('frameModule.js'); - let frameModule = {}; - - Cu.import(frameModuleURL, frameModule); - frameModule.contentObserver.unregister(); - Cu.unload(frameModuleURL); + // } + + // frameModule needs to be cleared too + let frameModuleURL = vAPI.getURL('frameModule.js'); + let frameModule = {}; + + Cu.import(frameModuleURL, frameModule); + frameModule.contentObserver.unregister(); + Cu.unload(frameModuleURL); }); vAPI.noTabId = '-1'; vAPI.isBehindTheSceneTabId = function (tabId) { - return tabId.toString() === '-1'; + return tabId.toString() === '-1'; }; vAPI.lastError = function () { - return null; + return null; }; })(this); diff --git a/js/vapi-messaging.js b/js/vapi-messaging.js index ef11094..b4f468f 100644 --- a/js/vapi-messaging.js +++ b/js/vapi-messaging.js @@ -27,111 +27,111 @@ (function () { Cu.import('chrome://ematrix/content/CallbackWrapper.jsm'); - + vAPI.messaging = { - get globalMessageManager() { + get globalMessageManager() { return Cc['@mozilla.org/globalmessagemanager;1'] .getService(Ci.nsIMessageListenerManager); - }, - frameScript: vAPI.getURL('frameScript.js'), - listeners: {}, - defaultHandler: null, - NOOPFUNC: function(){}, - UNHANDLED: 'vAPI.messaging.notHandled' + }, + frameScript: vAPI.getURL('frameScript.js'), + listeners: {}, + defaultHandler: null, + NOOPFUNC: function(){}, + UNHANDLED: 'vAPI.messaging.notHandled' }; vAPI.messaging.listen = function (listenerName, callback) { - this.listeners[listenerName] = callback; + this.listeners[listenerName] = callback; }; vAPI.messaging.onMessage = function ({target, data}) { - let messageManager = target.messageManager; + let messageManager = target.messageManager; - if (!messageManager) { + if (!messageManager) { // Message came from a popup, and its message manager is // not usable. So instead we broadcast to the parent // window. messageManager = - vAPI.browser. - getOwnerWindow(target.webNavigation - .QueryInterface(Ci.nsIDocShell) - .chromeEventHandler).messageManager; - } - - let channelNameRaw = data.channelName; - let pos = channelNameRaw.indexOf('|'); - let channelName = channelNameRaw.slice(pos + 1); - - let callback = vAPI.messaging.NOOPFUNC; - if (data.requestId !== undefined) { + vAPI.browser. + getOwnerWindow(target.webNavigation + .QueryInterface(Ci.nsIDocShell) + .chromeEventHandler).messageManager; + } + + let channelNameRaw = data.channelName; + let pos = channelNameRaw.indexOf('|'); + let channelName = channelNameRaw.slice(pos + 1); + + let callback = vAPI.messaging.NOOPFUNC; + if (data.requestId !== undefined) { callback = CallbackWrapper.factory(messageManager, - channelName, - channelNameRaw.slice(0, pos), - data.requestId).callback; - } + channelName, + channelNameRaw.slice(0, pos), + data.requestId).callback; + } - let sender = { + let sender = { tab: { - id: vAPI.tabs.manager.tabIdFromTarget(target) + id: vAPI.tabs.manager.tabIdFromTarget(target) } - }; + }; + + // Specific handler + let r = vAPI.messaging.UNHANDLED; + let listener = vAPI.messaging.listeners[channelName]; - // Specific handler - let r = vAPI.messaging.UNHANDLED; - let listener = vAPI.messaging.listeners[channelName]; - - if (typeof listener === 'function') { + if (typeof listener === 'function') { r = listener(data.msg, sender, callback); - } - if (r !== vAPI.messaging.UNHANDLED) { + } + if (r !== vAPI.messaging.UNHANDLED) { return; - } + } - // Default handler - r = vAPI.messaging.defaultHandler(data.msg, sender, callback); - if (r !== vAPI.messaging.UNHANDLED) { + // Default handler + r = vAPI.messaging.defaultHandler(data.msg, sender, callback); + if (r !== vAPI.messaging.UNHANDLED) { return; - } + } - console.error('eMatrix> messaging > unknown request: %o', data); + console.error('eMatrix> messaging > unknown request: %o', data); - // Unhandled: Need to callback anyways in case caller expected - // an answer, or else there is a memory leak on caller's side - callback(); + // Unhandled: Need to callback anyways in case caller expected + // an answer, or else there is a memory leak on caller's side + callback(); }; vAPI.messaging.setup = function (defaultHandler) { - // Already setup? - if (this.defaultHandler !== null) { + // Already setup? + if (this.defaultHandler !== null) { return; - } + } - if (typeof defaultHandler !== 'function') { + if (typeof defaultHandler !== 'function') { defaultHandler = function () { - return vAPI.messaging.UNHANDLED; - }; - } - - this.defaultHandler = defaultHandler; - this.globalMessageManager.addMessageListener(location.host - + ':background', - this.onMessage); - this.globalMessageManager.loadFrameScript(this.frameScript, true); - - vAPI.addCleanUpTask(function () { + return vAPI.messaging.UNHANDLED; + }; + } + + this.defaultHandler = defaultHandler; + this.globalMessageManager.addMessageListener(location.host + + ':background', + this.onMessage); + this.globalMessageManager.loadFrameScript(this.frameScript, true); + + vAPI.addCleanUpTask(function () { let gmm = vAPI.messaging.globalMessageManager; gmm.removeDelayedFrameScript(vAPI.messaging.frameScript); gmm.removeMessageListener(location.host + ':background', - vAPI.messaging.onMessage); - }); + vAPI.messaging.onMessage); + }); }; vAPI.messaging.broadcast = function (message) { - this.globalMessageManager - .broadcastAsyncMessage(location.host + ':broadcast', - JSON.stringify({ - broadcast: true, - msg: message})); + this.globalMessageManager + .broadcastAsyncMessage(location.host + ':broadcast', + JSON.stringify({ + broadcast: true, + msg: message})); }; })(); diff --git a/js/vapi-net.js b/js/vapi-net.js index ec12d2a..f8d1052 100644 --- a/js/vapi-net.js +++ b/js/vapi-net.js @@ -29,41 +29,41 @@ vAPI.net = {}; vAPI.net.registerListeners = function () { - this.onBeforeRequest.types = this.onBeforeRequest.types - ? new Set(this.onBeforeRequest.types) - : null; - - this.onBeforeSendHeaders.types = this.onBeforeSendHeaders.types - ? new Set(this.onBeforeSendHeaders.types) - : null; + this.onBeforeRequest.types = this.onBeforeRequest.types + ? new Set(this.onBeforeRequest.types) + : null; - let shouldLoadListenerMessageName = location.host + ':shouldLoad'; - let shouldLoadListener = function (e) { + this.onBeforeSendHeaders.types = this.onBeforeSendHeaders.types + ? new Set(this.onBeforeSendHeaders.types) + : null; + + let shouldLoadListenerMessageName = location.host + ':shouldLoad'; + let shouldLoadListener = function (e) { let details = e.data; let pendingReq = vAPI.httpObserver.createPendingRequest(details.url); pendingReq.rawType = details.rawType; pendingReq.tabId = vAPI.tabs.manager.tabIdFromTarget(e.target); - }; + }; - // https://github.com/gorhill/uMatrix/issues/200 - // We need this only for Firefox 34 and less: the tab id is derived from - // the origin of the message. - if (!vAPI.modernFirefox) { + // https://github.com/gorhill/uMatrix/issues/200 + // We need this only for Firefox 34 and less: the tab id is derived from + // the origin of the message. + if (!vAPI.modernFirefox) { vAPI.messaging.globalMessageManager - .addMessageListener(shouldLoadListenerMessageName, - shouldLoadListener); - } + .addMessageListener(shouldLoadListenerMessageName, + shouldLoadListener); + } - vAPI.httpObserver.register(); + vAPI.httpObserver.register(); - vAPI.addCleanUpTask(function () { + vAPI.addCleanUpTask(function () { if (!vAPI.modernFirefox) { - vAPI.messaging.globalMessageManager - .removeMessageListener(shouldLoadListenerMessageName, - shouldLoadListener); + vAPI.messaging.globalMessageManager + .removeMessageListener(shouldLoadListenerMessageName, + shouldLoadListener); } vAPI.httpObserver.unregister(); - }); + }); }; })(); diff --git a/js/vapi-storage.js b/js/vapi-storage.js index 8acb244..c94f595 100644 --- a/js/vapi-storage.js +++ b/js/vapi-storage.js @@ -29,60 +29,60 @@ // API matches that of chrome.storage.local: // https://developer.chrome.com/extensions/storage vAPI.storage = (function () { - let db = null; - let vacuumTimer = null; + let db = null; + let vacuumTimer = null; - let close = function () { + let close = function () { if (vacuumTimer !== null) { - clearTimeout(vacuumTimer); - vacuumTimer = null; + clearTimeout(vacuumTimer); + vacuumTimer = null; } - + if (db === null) { - return; + return; } - + db.asyncClose(); db = null; - }; + }; - let open = function () { + let open = function () { if (db !== null) { - return db; + return db; } - + // Create path let path = Services.dirsvc.get('ProfD', Ci.nsIFile); path.append('ematrix-data'); if (!path.exists()) { - path.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0774', 8)); + path.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0774', 8)); } if (!path.isDirectory()) { - throw Error('Should be a directory...'); + throw Error('Should be a directory...'); } - let path2 = Services.dirsvc.get('ProfD', Ci.nsIFile); - path2.append('extension-data'); - path2.append(location.host + '.sqlite'); - if (path2.exists()) { - path2.moveTo(path, location.host+'.sqlite'); - } + let path2 = Services.dirsvc.get('ProfD', Ci.nsIFile); + path2.append('extension-data'); + path2.append(location.host + '.sqlite'); + if (path2.exists()) { + path2.moveTo(path, location.host+'.sqlite'); + } - path.append(location.host + '.sqlite'); + path.append(location.host + '.sqlite'); // Open database try { - db = Services.storage.openDatabase(path); - if (db.connectionReady === false) { + db = Services.storage.openDatabase(path); + if (db.connectionReady === false) { db.asyncClose(); db = null; - } + } } catch (ex) { - // Ignore + // Ignore } if (db === null) { - return null; + return null; } // Database was opened, register cleanup task @@ -90,240 +90,240 @@ // Setup database db.createAsyncStatement('CREATE TABLE IF NOT EXISTS ' - +'"settings" ("name" ' - +'TEXT PRIMARY KEY NOT NULL, ' - +'"value" TEXT);') - .executeAsync(); + +'"settings" ("name" ' + +'TEXT PRIMARY KEY NOT NULL, ' + +'"value" TEXT);') + .executeAsync(); if (vacuum !== null) { - vacuumTimer = vAPI.setTimeout(vacuum, 60000); + vacuumTimer = vAPI.setTimeout(vacuum, 60000); } return db; - }; + }; - // Vacuum only once, and only while idle - let vacuum = function () { + // Vacuum only once, and only while idle + let vacuum = function () { vacuumTimer = null; if (db === null) { - return; + return; } let idleSvc = - Cc['@mozilla.org/widget/idleservice;1'] - .getService(Ci.nsIIdleService); - + Cc['@mozilla.org/widget/idleservice;1'] + .getService(Ci.nsIIdleService); + if (idleSvc.idleTime < 60000) { - vacuumTimer = vAPI.setTimeout(vacuum, 60000); - return; + vacuumTimer = vAPI.setTimeout(vacuum, 60000); + return; } - + db.createAsyncStatement('VACUUM').executeAsync(); vacuum = null; - }; + }; - // Execute a query - let runStatement = function (stmt, callback) { + // Execute a query + let runStatement = function (stmt, callback) { let result = {}; stmt.executeAsync({ - handleResult: function (rows) { + handleResult: function (rows) { if (!rows || typeof callback !== 'function') { - return; + return; } let row; while ((row = rows.getNextRow())) { - // we assume that there will be two columns, since we're - // using it only for preferences - // eMatrix: the above comment is obsolete - // (it's not used just for preferences - // anymore), but we still expect two columns. - let res = row.getResultByIndex(0); - result[res] = row.getResultByIndex(1); + // we assume that there will be two columns, since we're + // using it only for preferences + // eMatrix: the above comment is obsolete + // (it's not used just for preferences + // anymore), but we still expect two columns. + let res = row.getResultByIndex(0); + result[res] = row.getResultByIndex(1); } - }, - handleCompletion: function (reason) { + }, + handleCompletion: function (reason) { if (typeof callback === 'function' && reason === 0) { - callback(result); + callback(result); } - }, - handleError: function (error) { + }, + handleError: function (error) { console.error('SQLite error ', error.result, error.message); - + // Caller expects an answer regardless of failure. if (typeof callback === 'function' ) { - callback(null); + callback(null); } - }, + }, }); - }; + }; - let bindNames = function (stmt, names) { + let bindNames = function (stmt, names) { if (Array.isArray(names) === false || names.length === 0) { - return; + return; } - + let params = stmt.newBindingParamsArray(); - - for (let i=names.length-1; i>=0; --i) { - let bp = params.newBindingParams(); - bp.bindByName('name', names[i]); - params.addParams(bp); + + for (let i=names.length-1; i>=0; --i) { + let bp = params.newBindingParams(); + bp.bindByName('name', names[i]); + params.addParams(bp); } - + stmt.bindParameters(params); - }; + }; - let clear = function (callback) { + let clear = function (callback) { if (open() === null) { - if (typeof callback === 'function') { + if (typeof callback === 'function') { callback(); - } - return; + } + return; } - + runStatement(db.createAsyncStatement('DELETE FROM "settings";'), - callback); - }; + callback); + }; - let getBytesInUse = function (keys, callback) { + let getBytesInUse = function (keys, callback) { if (typeof callback !== 'function') { - return; + return; } if (open() === null) { - callback(0); - return; + callback(0); + return; } let stmt; if (Array.isArray(keys)) { - stmt = db.createAsyncStatement('SELECT "size" AS "size", ' - +'SUM(LENGTH("value")) ' - +'FROM "settings" WHERE ' - +'"name" = :name'); - bindNames(keys); + stmt = db.createAsyncStatement('SELECT "size" AS "size", ' + +'SUM(LENGTH("value")) ' + +'FROM "settings" WHERE ' + +'"name" = :name'); + bindNames(keys); } else { - stmt = db.createAsyncStatement('SELECT "size" AS "size", ' - +'SUM(LENGTH("value")) ' - +'FROM "settings"'); + stmt = db.createAsyncStatement('SELECT "size" AS "size", ' + +'SUM(LENGTH("value")) ' + +'FROM "settings"'); } runStatement(stmt, function (result) { - callback(result.size); + callback(result.size); }); - }; + }; - let read = function (details, callback) { + let read = function (details, callback) { if (typeof callback !== 'function') { - return; + return; } let prepareResult = function (result) { - for (let key in result) { + for (let key in result) { if (result.hasOwnProperty(key) === false) { - continue; + continue; } - + result[key] = JSON.parse(result[key]); - } - - if (typeof details === 'object' && details !== null) { + } + + if (typeof details === 'object' && details !== null) { for (let key in details) { - if (result.hasOwnProperty(key) === false) { + if (result.hasOwnProperty(key) === false) { result[key] = details[key]; - } + } } - } - - callback(result); + } + + callback(result); }; if (open() === null) { - prepareResult({}); - return; + prepareResult({}); + return; } let names = []; if (details !== null) { - if (Array.isArray(details)) { + if (Array.isArray(details)) { names = details; - } else if (typeof details === 'object') { + } else if (typeof details === 'object') { names = Object.keys(details); - } else { + } else { names = [details.toString()]; - } + } } let stmt; if (names.length === 0) { - stmt = db.createAsyncStatement('SELECT * FROM "settings"'); + stmt = db.createAsyncStatement('SELECT * FROM "settings"'); } else { - stmt = db.createAsyncStatement('SELECT * FROM "settings" ' - +'WHERE "name" = :name'); - bindNames(stmt, names); + stmt = db.createAsyncStatement('SELECT * FROM "settings" ' + +'WHERE "name" = :name'); + bindNames(stmt, names); } - + runStatement(stmt, prepareResult); - }; + }; - let remove = function (keys, callback) { + let remove = function (keys, callback) { if (open() === null) { - if (typeof callback === 'function') { + if (typeof callback === 'function') { callback(); - } - return; + } + return; } - + var stmt = db.createAsyncStatement('DELETE FROM "settings" ' - +'WHERE "name" = :name'); + +'WHERE "name" = :name'); bindNames(stmt, typeof keys === 'string' ? [keys] : keys); runStatement(stmt, callback); - }; - - let write = function (details, callback) { + }; + + let write = function (details, callback) { if (open() === null) { - if (typeof callback === 'function') { + if (typeof callback === 'function') { callback(); - } - return; + } + return; } - + let stmt = db.createAsyncStatement('INSERT OR REPLACE INTO ' - +'"settings" ("name", "value") ' - +'VALUES(:name, :value)'); + +'"settings" ("name", "value") ' + +'VALUES(:name, :value)'); let params = stmt.newBindingParamsArray(); - + for (let key in details) { - if (details.hasOwnProperty(key) === false) { + if (details.hasOwnProperty(key) === false) { continue; - } - - let bp = params.newBindingParams(); - bp.bindByName('name', key); - bp.bindByName('value', JSON.stringify(details[key])); - params.addParams(bp); + } + + let bp = params.newBindingParams(); + bp.bindByName('name', key); + bp.bindByName('value', JSON.stringify(details[key])); + params.addParams(bp); } - + if (params.length === 0) { - return; + return; } stmt.bindParameters(params); runStatement(stmt, callback); - }; + }; - // Export API - var api = { + // Export API + var api = { QUOTA_BYTES: 100 * 1024 * 1024, clear: clear, get: read, getBytesInUse: getBytesInUse, remove: remove, set: write - }; - - return api; + }; + + return api; })(); vAPI.cacheStorage = vAPI.storage; diff --git a/js/vapi-tabs.js b/js/vapi-tabs.js index 9fbecd6..bf1aeea 100644 --- a/js/vapi-tabs.js +++ b/js/vapi-tabs.js @@ -29,548 +29,548 @@ vAPI.tabs = {}; vAPI.tabs.registerListeners = function() { - vAPI.tabs.manager.start(); + vAPI.tabs.manager.start(); }; vAPI.tabs.get = function (tabId, callback) { - // eMatrix: the following might be obsoleted (though probably - // still relevant at least for Pale Moon.) - // - // Firefox: - // - // browser -> ownerDocument -> defaultView -> gBrowser -> browsers --+ - // ^ | - // | | - // +--------------------------------------------------------------+ - // - // browser (browser) - // contentTitle - // currentURI - // ownerDocument (XULDocument) - // defaultView (ChromeWindow) - // gBrowser (tabbrowser OR browser) - // browsers (browser) - // selectedBrowser - // selectedTab - // tabs (tab.tabbrowser-tab) - // - // Fennec: (what I figured so far) - // - // tab -> browser windows -> window -> BrowserApp -> tabs --+ - // ^ window | - // | | - // +-----------------------------------------------------------+ - // - // tab - // browser - // [manual search to go back to tab from list of windows] - let browser; - - if (tabId === null) { + // eMatrix: the following might be obsoleted (though probably + // still relevant at least for Pale Moon.) + // + // Firefox: + // + // browser -> ownerDocument -> defaultView -> gBrowser -> browsers --+ + // ^ | + // | | + // +--------------------------------------------------------------+ + // + // browser (browser) + // contentTitle + // currentURI + // ownerDocument (XULDocument) + // defaultView (ChromeWindow) + // gBrowser (tabbrowser OR browser) + // browsers (browser) + // selectedBrowser + // selectedTab + // tabs (tab.tabbrowser-tab) + // + // Fennec: (what I figured so far) + // + // tab -> browser windows -> window -> BrowserApp -> tabs --+ + // ^ window | + // | | + // +-----------------------------------------------------------+ + // + // tab + // browser + // [manual search to go back to tab from list of windows] + let browser; + + if (tabId === null) { browser = vAPI.tabs.manager.currentBrowser(); tabId = vAPI.tabs.manager.tabIdFromTarget(browser); - } else { + } else { browser = vAPI.tabs.manager.browserFromTabId(tabId); - } + } - // For internal use - if (typeof callback !== 'function') { + // For internal use + if (typeof callback !== 'function') { return browser; - } + } - if (!browser || !browser.currentURI) { + if (!browser || !browser.currentURI) { callback(); return; - } + } - let win = vAPI.browser.getOwnerWindow(browser); - let tabBrowser = vAPI.browser.getTabBrowser(win); + let win = vAPI.browser.getOwnerWindow(browser); + let tabBrowser = vAPI.browser.getTabBrowser(win); - callback({ + callback({ id: tabId, windowId: vAPI.window.idFromWindow(win), active: tabBrowser !== null - && browser === tabBrowser.selectedBrowser, + && browser === tabBrowser.selectedBrowser, url: browser.currentURI.asciiSpec, title: browser.contentTitle - }); + }); }; vAPI.tabs.getAllSync = function (window) { - let win; - let tabs = []; + let win; + let tabs = []; - for (let win of vAPI.window.getWindows()) { + for (let win of vAPI.window.getWindows()) { if (window && window !== win) { - continue; + continue; } let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { - continue; + continue; } // This can happens if a tab-less window is currently opened. // Example of a tab-less window: one opened from clicking // "View Page Source". if (!tabBrowser.tabs) { - continue; + continue; } for (let tab of tabBrowser.tabs) { - tabs.push(tab); + tabs.push(tab); } - } + } - return tabs; + return tabs; }; vAPI.tabs.getAll = function (callback) { - let tabs = []; + let tabs = []; - for (let browser of vAPI.tabs.manager.browsers()) { + for (let browser of vAPI.tabs.manager.browsers()) { let tab = vAPI.tabs.manager.tabFromBrowser(browser); - + if (tab === null) { - continue; + continue; } - + if (tab.hasAttribute('pending')) { - continue; + continue; } - + tabs.push({ - id: vAPI.tabs.manager.tabIdFromTarget(browser), - url: browser.currentURI.asciiSpec + id: vAPI.tabs.manager.tabIdFromTarget(browser), + url: browser.currentURI.asciiSpec }); - } + } - callback(tabs); + callback(tabs); }; vAPI.tabs.open = function (details) { - // properties of the details object: - // + url - the address that will be opened - // + tabId:- the tab is used if set, instead of creating a new one - // + index: - undefined: end of the list, -1: following tab, or - // after index - // + active: - opens the tab in background - true and undefined: - // foreground - // + select: - if a tab is already opened with that url, then - // select it instead of opening a new one - if (!details.url) { + // properties of the details object: + // + url - the address that will be opened + // + tabId:- the tab is used if set, instead of creating a new one + // + index: - undefined: end of the list, -1: following tab, or + // after index + // + active: - opens the tab in background - true and undefined: + // foreground + // + select: - if a tab is already opened with that url, then + // select it instead of opening a new one + if (!details.url) { return null; - } - - // extension pages - if (/^[\w-]{2,}:/.test(details.url) === false) { + } + + // extension pages + if (/^[\w-]{2,}:/.test(details.url) === false) { details.url = vAPI.getURL(details.url); - } + } - if (details.select) { + if (details.select) { let URI = Services.io.newURI(details.url, null, null); for (let tab of this.getAllSync()) { - let browser = vAPI.tabs.manager.browserFromTarget(tab); - - // https://github.com/gorhill/uBlock/issues/2558 - if (browser === null) { - continue; - } - - // Or simply .equals if we care about the fragment - if (URI.equalsExceptRef(browser.currentURI) === false) { + let browser = vAPI.tabs.manager.browserFromTarget(tab); + + // https://github.com/gorhill/uBlock/issues/2558 + if (browser === null) { continue; - } + } - this.select(tab); + // Or simply .equals if we care about the fragment + if (URI.equalsExceptRef(browser.currentURI) === false) { + continue; + } - // Update URL if fragment is different - if (URI.equals(browser.currentURI) === false) { + this.select(tab); + + // Update URL if fragment is different + if (URI.equals(browser.currentURI) === false) { browser.loadURI(URI.asciiSpec); - } - - return; + } + + return; } - } + } - if (details.active === undefined) { + if (details.active === undefined) { details.active = true; - } + } - if (details.tabId) { + if (details.tabId) { let tab = vAPI.tabs.manager.browserFromTabId(details.tabId); - + if (tab) { - vAPI.tabs.manager.browserFromTarget(tab).loadURI(details.url); - return; + vAPI.tabs.manager.browserFromTarget(tab).loadURI(details.url); + return; } - } + } - // Open in a standalone window - if (details.popup === true) { + // Open in a standalone window + if (details.popup === true) { Services.ww.openWindow(self, - details.url, - null, - 'location=1,menubar=1,personalbar=1,' - +'resizable=1,toolbar=1', - null); + details.url, + null, + 'location=1,menubar=1,personalbar=1,' + +'resizable=1,toolbar=1', + null); return; - } + } - let win = vAPI.window.getCurrentWindow(); - let tabBrowser = vAPI.browser.getTabBrowser(win); - - if (tabBrowser === null) { + let win = vAPI.window.getCurrentWindow(); + let tabBrowser = vAPI.browser.getTabBrowser(win); + + if (tabBrowser === null) { return; - } + } - if (details.index === -1) { + if (details.index === -1) { details.index = - tabBrowser.browsers.indexOf(tabBrowser.selectedBrowser) + 1; - } + tabBrowser.browsers.indexOf(tabBrowser.selectedBrowser) + 1; + } - let tab = tabBrowser.loadOneTab(details.url, { - inBackground: !details.active - }); + let tab = tabBrowser.loadOneTab(details.url, { + inBackground: !details.active + }); - if (details.index !== undefined) { + if (details.index !== undefined) { tabBrowser.moveTabTo(tab, details.index); - } + } }; vAPI.tabs.replace = function (tabId, url) { - // Replace the URL of a tab. Noop if the tab does not exist. - let targetURL = url; + // Replace the URL of a tab. Noop if the tab does not exist. + let targetURL = url; - // extension pages - if (/^[\w-]{2,}:/.test(targetURL) !== true) { + // extension pages + if (/^[\w-]{2,}:/.test(targetURL) !== true) { targetURL = vAPI.getURL(targetURL); - } + } - let browser = vAPI.tabs.manager.browserFromTabId(tabId); - if (browser) { + let browser = vAPI.tabs.manager.browserFromTabId(tabId); + if (browser) { browser.loadURI(targetURL); - } + } }; function removeInternal(tab, tabBrowser) { - if (tabBrowser) { - tabBrowser.removeTab(tab); - } + if (tabBrowser) { + tabBrowser.removeTab(tab); + } } vAPI.tabs.remove = function (tabId) { - let browser = vAPI.tabs.manager.browserFromTabId(tabId); - if (!browser) { + let browser = vAPI.tabs.manager.browserFromTabId(tabId); + if (!browser) { return; - } - - let tab = vAPI.tabs.manager.tabFromBrowser(browser); - if (!tab) { + } + + let tab = vAPI.tabs.manager.tabFromBrowser(browser); + if (!tab) { return; - } - - removeInternal(tab, - vAPI.browser.getTabBrowser - (vAPI.browser.getOwnerWindow(browser))); + } + + removeInternal(tab, + vAPI.browser.getTabBrowser + (vAPI.browser.getOwnerWindow(browser))); }; vAPI.tabs.reload = function (tabId) { - let browser = vAPI.tabs.manager.browserFromTabId(tabId); - if (!browser) { + let browser = vAPI.tabs.manager.browserFromTabId(tabId); + if (!browser) { return; - } + } - browser.webNavigation.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE); + browser.webNavigation.reload(Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE); }; vAPI.tabs.select = function (tab) { - if (typeof tab !== 'object') { + if (typeof tab !== 'object') { tab = vAPI.tabs.manager - .tabFromBrowser(vAPI.tabs.manager.browserFromTabId(tab)); - } - if (!tab) { + .tabFromBrowser(vAPI.tabs.manager.browserFromTabId(tab)); + } + if (!tab) { return; - } + } - // https://github.com/gorhill/uBlock/issues/470 - let win = vAPI.browser.getOwnerWindow(tab); - win.focus(); + // https://github.com/gorhill/uBlock/issues/470 + let win = vAPI.browser.getOwnerWindow(tab); + win.focus(); - let tabBrowser = vAPI.browser.getTabBrowser(win); - if (tabBrowser) { + let tabBrowser = vAPI.browser.getTabBrowser(win); + if (tabBrowser) { tabBrowser.selectedTab = tab; - } + } }; vAPI.tabs.injectScript = function (tabId, details, callback) { - let browser = vAPI.tabs.manager.browserFromTabId(tabId); - if (!browser) { + let browser = vAPI.tabs.manager.browserFromTabId(tabId); + if (!browser) { return; - } + } - if (typeof details.file !== 'string') { + if (typeof details.file !== 'string') { return; - } - - details.file = vAPI.getURL(details.file); - browser.messageManager.sendAsyncMessage(location.host + ':broadcast', - JSON.stringify({ - broadcast: true, - channelName: 'vAPI', - msg: { - cmd: 'injectScript', - details: details - } - })); - - if (typeof callback === 'function') { + } + + details.file = vAPI.getURL(details.file); + browser.messageManager.sendAsyncMessage(location.host + ':broadcast', + JSON.stringify({ + broadcast: true, + channelName: 'vAPI', + msg: { + cmd: 'injectScript', + details: details + } + })); + + if (typeof callback === 'function') { vAPI.setTimeout(callback, 13); - } + } }; vAPI.tabs.manager = (function () { - // TODO: find out whether we need a janitor to take care of stale entries. + // TODO: find out whether we need a janitor to take care of stale entries. - // https://github.com/gorhill/uMatrix/issues/540 - // Use only weak references to hold onto browser references. - let browserToTabIdMap = new WeakMap(); - let tabIdToBrowserMap = new Map(); - let tabIdGenerator = 1; + // https://github.com/gorhill/uMatrix/issues/540 + // Use only weak references to hold onto browser references. + let browserToTabIdMap = new WeakMap(); + let tabIdToBrowserMap = new Map(); + let tabIdGenerator = 1; - let indexFromBrowser = function (browser) { + let indexFromBrowser = function (browser) { if (!browser) { - return -1; + return -1; } - let win = vAPI.browser.getOwnerWindow(browser); + let win = vAPI.browser.getOwnerWindow(browser); if (!win) { - return -1; + return -1; } - + let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { - return -1; + return -1; } - + // This can happen, for example, the `view-source:` // window, there is no tabbrowser object, the browser // object sits directly in the window. if (tabBrowser === browser) { - return 0; + return 0; } - + return tabBrowser.browsers.indexOf(browser); - }; + }; - let indexFromTarget = function (target) { + let indexFromTarget = function (target) { return indexFromBrowser(browserFromTarget(target)); - }; + }; - let tabFromBrowser = function (browser) { + let tabFromBrowser = function (browser) { let i = indexFromBrowser(browser); if (i === -1) { - return null; + return null; } - + let win = vAPI.browser.getOwnerWindow(browser); if (!win) { - return null; + return null; } - + let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { - return null; + return null; } - + if (!tabBrowser.tabs || i >= tabBrowser.tabs.length) { - return null; + return null; } - + return tabBrowser.tabs[i]; - }; + }; - let browserFromTarget = function (target) { + let browserFromTarget = function (target) { if (!target) { - return null; + return null; } - + if (target.linkedPanel) { - // target is a tab - target = target.linkedBrowser; + // target is a tab + target = target.linkedBrowser; } - + if (target.localName !== 'browser') { - return null; + return null; } - + return target; - }; + }; - let tabIdFromTarget = function (target) { + let tabIdFromTarget = function (target) { let browser = browserFromTarget(target); if (browser === null) { - return vAPI.noTabId; + return vAPI.noTabId; } - + let tabId = browserToTabIdMap.get(browser); if (tabId === undefined) { - tabId = '' + tabIdGenerator++; - browserToTabIdMap.set(browser, tabId); - tabIdToBrowserMap.set(tabId, Cu.getWeakReference(browser)); + tabId = '' + tabIdGenerator++; + browserToTabIdMap.set(browser, tabId); + tabIdToBrowserMap.set(tabId, Cu.getWeakReference(browser)); } - + return tabId; - }; + }; - let browserFromTabId = function (tabId) { + let browserFromTabId = function (tabId) { let weakref = tabIdToBrowserMap.get(tabId); let browser = weakref && weakref.get(); - + return browser || null; - }; + }; - let currentBrowser = function () { + let currentBrowser = function () { let win = vAPI.window.getCurrentWindow(); - + // https://github.com/gorhill/uBlock/issues/399 // getTabBrowser() can return null at browser launch time. let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { - return null; + return null; } - + return browserFromTarget(tabBrowser.selectedTab); - }; + }; - let removeBrowserEntry = function (tabId, browser) { + let removeBrowserEntry = function (tabId, browser) { if (tabId && tabId !== vAPI.noTabId) { - vAPI.tabs.onClosed(tabId); - delete vAPI.toolbarButton.tabs[tabId]; - tabIdToBrowserMap.delete(tabId); + vAPI.tabs.onClosed(tabId); + delete vAPI.toolbarButton.tabs[tabId]; + tabIdToBrowserMap.delete(tabId); } - + if (browser) { - browserToTabIdMap.delete(browser); + browserToTabIdMap.delete(browser); } - }; + }; - let removeTarget = function (target) { + let removeTarget = function (target) { onClose({ - target: target - }); - }; + target: target + }); + }; - let getAllBrowsers = function () { + let getAllBrowsers = function () { let browsers = []; - for (let [tabId, weakref] of tabIdToBrowserMap) { - let browser = weakref.get(); - - // TODO: Maybe call removeBrowserEntry() if the - // browser no longer exists? - if (browser) { + for (let [tabId, weakref] of tabIdToBrowserMap) { + let browser = weakref.get(); + + // TODO: Maybe call removeBrowserEntry() if the + // browser no longer exists? + if (browser) { browsers.push(browser); - } + } } - + return browsers; - }; - - // var onOpen = function (target) { - // var tabId = tabIdFromTarget(target); - // var browser = browserFromTabId(tabId); - // vAPI.tabs.onNavigation({ - // frameId: 0, - // tabId: tabId, - // url: browser.currentURI.asciiSpec, - // }); - // }; - - var onShow = function ({target}) { + }; + + // var onOpen = function (target) { + // var tabId = tabIdFromTarget(target); + // var browser = browserFromTabId(tabId); + // vAPI.tabs.onNavigation({ + // frameId: 0, + // tabId: tabId, + // url: browser.currentURI.asciiSpec, + // }); + // }; + + var onShow = function ({target}) { tabIdFromTarget(target); - }; + }; - var onClose = function ({target}) { + var onClose = function ({target}) { // target is tab in Firefox, browser in Fennec let browser = browserFromTarget(target); let tabId = browserToTabIdMap.get(browser); removeBrowserEntry(tabId, browser); - }; + }; - var onSelect = function ({target}) { - // This is an entry point: when creating a new tab, it is - // not always reported through onLocationChanged... - // Sigh. It is "reported" here however. + var onSelect = function ({target}) { + // This is an entry point: when creating a new tab, it is + // not always reported through onLocationChanged... + // Sigh. It is "reported" here however. let browser = browserFromTarget(target); let tabId = browserToTabIdMap.get(browser); - if (tabId === undefined) { - tabId = tabIdFromTarget(target); - vAPI.tabs.onNavigation({ + if (tabId === undefined) { + tabId = tabIdFromTarget(target); + vAPI.tabs.onNavigation({ frameId: 0, tabId: tabId, url: browser.currentURI.asciiSpec - }); + }); } - + vAPI.setIcon(tabId, vAPI.browser.getOwnerWindow(target)); - }; + }; - let locationChangedMessageName = location.host + ':locationChanged'; + let locationChangedMessageName = location.host + ':locationChanged'; - let onLocationChanged = function (e) { + let onLocationChanged = function (e) { let details = e.data; // Ignore notifications related to our popup if (details.url.lastIndexOf(vAPI.getURL('popup.html'), 0) === 0) { - return; + return; } let browser = e.target; let tabId = tabIdFromTarget(browser); if (tabId === vAPI.noTabId) { - return; + return; } // LOCATION_CHANGE_SAME_DOCUMENT = "did not load a new document" if (details.flags - & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) { - vAPI.tabs.onUpdated(tabId, {url: details.url}, { + & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) { + vAPI.tabs.onUpdated(tabId, {url: details.url}, { frameId: 0, tabId: tabId, url: browser.currentURI.asciiSpec - }); - return; + }); + return; } // https://github.com/chrisaljoudi/uBlock/issues/105 // Allow any kind of pages vAPI.tabs.onNavigation({ - frameId: 0, - tabId: tabId, - url: details.url + frameId: 0, + tabId: tabId, + url: details.url }); - }; + }; - let attachToTabBrowser = function (window) { + let attachToTabBrowser = function (window) { if (typeof vAPI.toolbarButton.attachToNewWindow === 'function') { - vAPI.toolbarButton.attachToNewWindow(window); + vAPI.toolbarButton.attachToNewWindow(window); } let tabBrowser = vAPI.browser.getTabBrowser(window); if (tabBrowser === null) { - return; + return; } let tabContainer; if (tabBrowser.deck) { // Fennec - tabContainer = tabBrowser.deck; + tabContainer = tabBrowser.deck; } else if (tabBrowser.tabContainer) { - // Firefox - tabContainer = tabBrowser.tabContainer; + // Firefox + tabContainer = tabBrowser.tabContainer; } // https://github.com/gorhill/uBlock/issues/697 @@ -579,51 +579,51 @@ // of session restore -- it is set *after* the event is // fired in such case. if (tabContainer) { - tabContainer.addEventListener('TabShow', onShow); - tabContainer.addEventListener('TabClose', onClose); - // when new window is opened TabSelect doesn't run on - // the selected tab? - tabContainer.addEventListener('TabSelect', onSelect); - } - }; - - var canAttachToTabBrowser = function (window) { - // https://github.com/gorhill/uBlock/issues/906 - // Ensure the environment is ready before trying to attaching. + tabContainer.addEventListener('TabShow', onShow); + tabContainer.addEventListener('TabClose', onClose); + // when new window is opened TabSelect doesn't run on + // the selected tab? + tabContainer.addEventListener('TabSelect', onSelect); + } + }; + + var canAttachToTabBrowser = function (window) { + // https://github.com/gorhill/uBlock/issues/906 + // Ensure the environment is ready before trying to attaching. let document = window && window.document; if (!document || document.readyState !== 'complete') { - return false; + return false; } // On some platforms, the tab browser isn't immediately - // available, try waiting a bit if this + // available, try waiting a bit if this // https://github.com/gorhill/uBlock/issues/763 // Not getting a tab browser should not prevent from // attaching ourself to the window. let tabBrowser = vAPI.browser.getTabBrowser(window); if (tabBrowser === null) { - return false; + return false; } return vAPI.window.toBrowserWindow(window) !== null; - }; + }; - let onWindowLoad = function (win) { + let onWindowLoad = function (win) { vAPI.deferUntil(canAttachToTabBrowser.bind(null, win), - attachToTabBrowser.bind(null, win)); - }; + attachToTabBrowser.bind(null, win)); + }; - let onWindowUnload = function (win) { + let onWindowUnload = function (win) { let tabBrowser = vAPI.browser.getTabBrowser(win); if (tabBrowser === null) { - return; + return; } let tabContainer = tabBrowser.tabContainer; if (tabContainer) { - tabContainer.removeEventListener('TabShow', onShow); - tabContainer.removeEventListener('TabClose', onClose); - tabContainer.removeEventListener('TabSelect', onSelect); + tabContainer.removeEventListener('TabShow', onShow); + tabContainer.removeEventListener('TabClose', onClose); + tabContainer.removeEventListener('TabSelect', onSelect); } // https://github.com/gorhill/uBlock/issues/574 @@ -631,85 +631,85 @@ // sometimes the window IS the tab. let tabs; if (tabBrowser.tabs) { - tabs = tabBrowser.tabs; + tabs = tabBrowser.tabs; } else if (tabBrowser.localName === 'browser') { - tabs = [tabBrowser]; + tabs = [tabBrowser]; } else { - tabs = []; + tabs = []; } let browser; - let URI; - let tabId; - for (let i=tabs.length-1; i>=0; --i) { - let tab = tabs[i]; - browser = browserFromTarget(tab); - if (browser === null) { + let URI; + let tabId; + for (let i=tabs.length-1; i>=0; --i) { + let tab = tabs[i]; + browser = browserFromTarget(tab); + if (browser === null) { continue; - } - - URI = browser.currentURI; - // Close extension tabs - if (URI.schemeIs('chrome') && URI.host === location.host) { + } + + URI = browser.currentURI; + // Close extension tabs + if (URI.schemeIs('chrome') && URI.host === location.host) { removeInternal(tab, vAPI.browser.getTabBrowser(win)); - } - - tabId = browserToTabIdMap.get(browser); - if (tabId !== undefined) { + } + + tabId = browserToTabIdMap.get(browser); + if (tabId !== undefined) { removeBrowserEntry(tabId, browser); tabIdToBrowserMap.delete(tabId); - } - browserToTabIdMap.delete(browser); + } + browserToTabIdMap.delete(browser); } - }; + }; - var start = function () { - // Initialize map with existing active tabs + var start = function () { + // Initialize map with existing active tabs let tabBrowser; - let tabs; + let tabs; for (let win of vAPI.window.getWindows()) { - onWindowLoad(win); - - tabBrowser = vAPI.browser.getTabBrowser(win); - if (tabBrowser === null) { + onWindowLoad(win); + + tabBrowser = vAPI.browser.getTabBrowser(win); + if (tabBrowser === null) { continue; - } - - for (let tab of tabBrowser.tabs) { + } + + for (let tab of tabBrowser.tabs) { if (!tab.hasAttribute('pending')) { - tabIdFromTarget(tab); + tabIdFromTarget(tab); } - } + } } vAPI.window.onOpenWindow = onWindowLoad; vAPI.window.onCloseWindow = onWindowUnload; vAPI.messaging.globalMessageManager - .addMessageListener(locationChangedMessageName, - onLocationChanged); - }; + .addMessageListener(locationChangedMessageName, + onLocationChanged); + }; - let stop = function () { + let stop = function () { vAPI.window.onOpenWindow = null; vAPI.window.onCloseWindow = null; vAPI.messaging.globalMessageManager - .removeMessageListener(locationChangedMessageName, - onLocationChanged); + .removeMessageListener(locationChangedMessageName, + onLocationChanged); for (let win of vAPI.window.getWindows()) { - onWindowUnload(win); + onWindowUnload(win); } browserToTabIdMap = new WeakMap(); tabIdToBrowserMap.clear(); - }; + }; - vAPI.addCleanUpTask(stop); + vAPI.addCleanUpTask(stop); - return { + return { browsers: getAllBrowsers, browserFromTabId: browserFromTabId, browserFromTarget: browserFromTarget, @@ -719,6 +719,6 @@ start: start, tabFromBrowser: tabFromBrowser, tabIdFromTarget: tabIdFromTarget - }; + }; })(); })(); diff --git a/js/vapi-window.js b/js/vapi-window.js index 1619ead..c512135 100644 --- a/js/vapi-window.js +++ b/js/vapi-window.js @@ -27,157 +27,157 @@ (function () { vAPI.window = (function () { - let windowToIdMap = new Map(); - let windowIdGenerator = 1; - let api = { + let windowToIdMap = new Map(); + let windowIdGenerator = 1; + let api = { onOpenWindow: null, onCloseWindow: null - }; - - // https://github.com/gorhill/uMatrix/issues/586 This is - // necessary hack because on SeaMonkey 2.40, for unknown - // reasons private windows do not have the attribute - // `windowtype` set to `navigator:browser`. As a fallback, the - // code here will also test whether the id attribute is - // `main-window`. - api.toBrowserWindow = function (win) { + }; + + // https://github.com/gorhill/uMatrix/issues/586 This is + // necessary hack because on SeaMonkey 2.40, for unknown + // reasons private windows do not have the attribute + // `windowtype` set to `navigator:browser`. As a fallback, the + // code here will also test whether the id attribute is + // `main-window`. + api.toBrowserWindow = function (win) { let docElement = win && win.document - && win.document.documentElement; - + && win.document.documentElement; + if (!docElement) { - return null; + return null; } if (vAPI.thunderbird) { - return docElement.getAttribute('windowtype') === 'mail:3pane' - ? win - : null; + return docElement.getAttribute('windowtype') === 'mail:3pane' + ? win + : null; } - + return docElement.getAttribute('windowtype') === 'navigator:browser' - || docElement.getAttribute('id') === 'main-window' - ? win - : null; - }; + || docElement.getAttribute('id') === 'main-window' + ? win + : null; + }; - api.getWindows = function () { + api.getWindows = function () { return windowToIdMap.keys(); - }; + }; - api.idFromWindow = function (win) { + api.idFromWindow = function (win) { return windowToIdMap.get(win) || 0; - }; + }; - api.getCurrentWindow = function () { + api.getCurrentWindow = function () { return this.toBrowserWindow(Services.wm.getMostRecentWindow(null)); - }; + }; - let addWindow = function (win) { + let addWindow = function (win) { if (!win || windowToIdMap.has(win)) { - return; + return; } - + windowToIdMap.set(win, windowIdGenerator++); - + if (typeof api.onOpenWindow === 'function') { - api.onOpenWindow(win); + api.onOpenWindow(win); } - }; + }; - let removeWindow = function (win) { + let removeWindow = function (win) { if (!win || windowToIdMap.delete(win) !== true) { - return; + return; } - + if (typeof api.onCloseWindow === 'function') { - api.onCloseWindow(win); + api.onCloseWindow(win); } - }; + }; - // https://github.com/gorhill/uMatrix/issues/357 - // Use nsIWindowMediator for being notified of opened/closed windows. - let listeners = { + // https://github.com/gorhill/uMatrix/issues/357 + // Use nsIWindowMediator for being notified of opened/closed windows. + let listeners = { onOpenWindow: function (aWindow) { - let win; - try { + let win; + try { win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindow); - } catch (e) { - // Ignore - } - - addWindow(win); + } catch (e) { + // Ignore + } + + addWindow(win); }, onCloseWindow: function (aWindow) { - let win; - try { + let win; + try { win = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindow); - } catch (e) { - // Ignore - } - - removeWindow(win); + } catch (e) { + // Ignore + } + + removeWindow(win); }, observe: function (aSubject, topic) { - let win; - try { + let win; + try { win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindow); - } catch (e) { - // Ignore - } - - if (!win) { - return; - } - - switch (topic) { - case 'domwindowopened': - addWindow(win); - break; - case 'domwindowclosed': - removeWindow(win); - break; - default: - console.error('unknown observer topic'); - break; - } + } catch (e) { + // Ignore + } + + if (!win) { + return; + } + + switch (topic) { + case 'domwindowopened': + addWindow(win); + break; + case 'domwindowclosed': + removeWindow(win); + break; + default: + console.error('unknown observer topic'); + break; + } } - }; + }; - (function() { + (function() { let winumerator; winumerator = Services.wm.getEnumerator(null); while (winumerator.hasMoreElements()) { - let win = winumerator.getNext(); - - if (!win.closed) { + let win = winumerator.getNext(); + + if (!win.closed) { windowToIdMap.set(win, windowIdGenerator++); - } + } } winumerator = Services.ww.getWindowEnumerator(); while (winumerator.hasMoreElements()) { - let win = winumerator.getNext() + let win = winumerator.getNext() .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindow); - - if (!win.closed) { + + if (!win.closed) { windowToIdMap.set(win, windowIdGenerator++); - } + } } Services.wm.addListener(listeners); Services.ww.registerNotification(listeners); - })(); + })(); - vAPI.addCleanUpTask(function() { + vAPI.addCleanUpTask(function() { Services.wm.removeListener(listeners); Services.ww.unregisterNotification(listeners); windowToIdMap.clear(); - }); + }); - return api; + return api; })(); })(); @@ -29,47 +29,47 @@ ηMatrix.XAL = (function(){ -/******************************************************************************/ + /******************************************************************************/ -var exports = {}; -var noopFunc = function(){}; + var exports = {}; + var noopFunc = function(){}; -/******************************************************************************/ + /******************************************************************************/ -exports.keyvalSetOne = function(key, val, callback) { - var bin = {}; - bin[key] = val; - vAPI.storage.set(bin, callback || noopFunc); -}; + exports.keyvalSetOne = function(key, val, callback) { + var bin = {}; + bin[key] = val; + vAPI.storage.set(bin, callback || noopFunc); + }; -/******************************************************************************/ + /******************************************************************************/ -exports.keyvalGetOne = function(key, callback) { - vAPI.storage.get(key, callback); -}; + exports.keyvalGetOne = function(key, callback) { + vAPI.storage.get(key, callback); + }; -/******************************************************************************/ + /******************************************************************************/ -exports.keyvalSetMany = function(dict, callback) { - vAPI.storage.set(dict, callback || noopFunc); -}; + exports.keyvalSetMany = function(dict, callback) { + vAPI.storage.set(dict, callback || noopFunc); + }; -/******************************************************************************/ + /******************************************************************************/ -exports.keyvalRemoveOne = function(key, callback) { - vAPI.storage.remove(key, callback || noopFunc); -}; + exports.keyvalRemoveOne = function(key, callback) { + vAPI.storage.remove(key, callback || noopFunc); + }; -/******************************************************************************/ + /******************************************************************************/ -exports.keyvalRemoveAll = function(callback) { - vAPI.storage.clear(callback || noopFunc); -}; + exports.keyvalRemoveAll = function(callback) { + vAPI.storage.clear(callback || noopFunc); + }; -/******************************************************************************/ + /******************************************************************************/ -return exports; + return exports; -/******************************************************************************/ + /******************************************************************************/ })(); |