diff --git a/.jshintrc b/.jshintrc index 0a41460..a7b9bc0 100644 --- a/.jshintrc +++ b/.jshintrc @@ -3,14 +3,15 @@ "devel": true, "eqeqeq": true, "esnext": true, - "strict": "global", - "undef": true, - "unused": true, "globals": { + "browser": false, // global variable in Firefox, Edge "self": false, "chrome": false, "vAPI": false, - "µMatrix": false, - "Components": false // global variable in Firefox - } + "µMatrix": false + }, + "strict": "global", + "undef": true, + "unused": true, + "validthis": true } diff --git a/platform/chromium/vapi-background.js b/platform/chromium/vapi-background.js index 757eaea..c7483c8 100644 --- a/platform/chromium/vapi-background.js +++ b/platform/chromium/vapi-background.js @@ -20,8 +20,6 @@ Home: https://github.com/gorhill/uMatrix */ -/* global self, µMatrix */ - // For background page 'use strict'; @@ -37,22 +35,13 @@ var vAPI = self.vAPI = self.vAPI || {}; var chrome = self.chrome; var manifest = chrome.runtime.getManifest(); -vAPI.chrome = true; - -vAPI.webextFlavor = undefined; -if ( - self.browser instanceof Object && - typeof self.browser.runtime.getBrowserInfo === 'function' -) { - self.browser.runtime.getBrowserInfo().then(function(info) { - vAPI.webextFlavor = info.vendor + '-' + info.name + '-' + info.version; - }); -} else { - vAPI.webextFlavor = ''; -} - var noopFunc = function(){}; +// https://code.google.com/p/chromium/issues/detail?id=410868#c8 +var resetLastError = function() { + void chrome.runtime.lastError; +}; + /******************************************************************************/ // https://github.com/gorhill/uMatrix/issues/234 @@ -96,10 +85,13 @@ vAPI.tabs = {}; /******************************************************************************/ vAPI.isBehindTheSceneTabId = function(tabId) { - return tabId.toString() === '-1'; + if ( typeof tabId === 'string' ) { debugger; } + return tabId < 0; }; -vAPI.noTabId = '-1'; +vAPI.unsetTabId = 0; +vAPI.noTabId = -1; // definitely not any existing tab +vAPI.anyTabId = -2; // one of the existing tab /******************************************************************************/ @@ -126,12 +118,10 @@ vAPI.tabs.registerListeners = function() { var onCreatedNavigationTarget = function(details) { //console.debug('onCreatedNavigationTarget: tab id %d = "%s"', details.tabId, details.url); if ( reGoodForWebRequestAPI.test(details.url) ) { return; } - details.tabId = details.tabId.toString(); onNavigationClient(details); }; var onUpdated = function(tabId, changeInfo, tab) { - tabId = tabId.toString(); onUpdatedClient(tabId, changeInfo, tab); }; @@ -140,13 +130,12 @@ vAPI.tabs.registerListeners = function() { if ( details.frameId !== 0 ) { return; } - details.tabId = details.tabId.toString(); onNavigationClient(details); //console.debug('onCommitted: tab id %d = "%s"', details.tabId, details.url); }; var onClosed = function(tabId) { - onClosedClient(tabId.toString()); + onClosedClient(tabId); }; chrome.webNavigation.onCreatedNavigationTarget.addListener(onCreatedNavigationTarget); @@ -161,29 +150,18 @@ vAPI.tabs.registerListeners = function() { vAPI.tabs.get = function(tabId, callback) { var onTabReady = function(tab) { - // https://code.google.com/p/chromium/issues/detail?id=410868#c8 - if ( chrome.runtime.lastError ) { - } - if ( tab instanceof Object ) { - tab.id = tab.id.toString(); - } + resetLastError(); callback(tab); }; if ( tabId !== null ) { - if ( typeof tabId === 'string' ) { - tabId = parseInt(tabId, 10); - } chrome.tabs.get(tabId, onTabReady); return; } var onTabReceived = function(tabs) { - // https://code.google.com/p/chromium/issues/detail?id=410868#c8 - if ( chrome.runtime.lastError ) { - } + resetLastError(); var tab = null; if ( Array.isArray(tabs) && tabs.length !== 0 ) { tab = tabs[0]; - tab.id = tab.id.toString(); } callback(tab); }; @@ -193,16 +171,7 @@ vAPI.tabs.get = function(tabId, callback) { /******************************************************************************/ vAPI.tabs.getAll = function(callback) { - var onTabsReady = function(tabs) { - if ( Array.isArray(tabs) ) { - var i = tabs.length; - while ( i-- ) { - tabs[i].id = tabs[i].id.toString(); - } - } - callback(tabs); - }; - chrome.tabs.query({ url: '' }, onTabsReady); + chrome.tabs.query({ url: '' }, callback); }; /******************************************************************************/ @@ -254,7 +223,7 @@ vAPI.tabs.open = function(details) { } // update doesn't accept index, must use move - chrome.tabs.update(parseInt(details.tabId, 10), _details, function(tab) { + chrome.tabs.update(details.tabId, _details, function(tab) { // if the tab doesn't exist if ( vAPI.lastError() ) { chrome.tabs.create(_details, focusWindow); @@ -292,7 +261,7 @@ vAPI.tabs.open = function(details) { } chrome.tabs.query({ url: targetURL }, function(tabs) { - if ( chrome.runtime.lastError ) { /* noop */ } + resetLastError(); var tab = Array.isArray(tabs) && tabs[0]; if ( tab ) { chrome.tabs.update(tab.id, { active: true }, function(tab) { @@ -316,40 +285,21 @@ vAPI.tabs.replace = function(tabId, url) { targetURL = vAPI.getURL(targetURL); } - if ( typeof tabId !== 'number' ) { - tabId = parseInt(tabId, 10); - if ( isNaN(tabId) ) { - return; - } - } + if ( typeof tabId !== 'number' || tabId < 0 ) { return; } - chrome.tabs.update(tabId, { url: targetURL }, function() { - // this prevent console error - if ( chrome.runtime.lastError ) { - return; - } - }); + chrome.tabs.update(tabId, { url: targetURL }, resetLastError); }; /******************************************************************************/ vAPI.tabs.remove = function(tabId) { - var onTabRemoved = function() { - if ( vAPI.lastError() ) { - } - }; - chrome.tabs.remove(parseInt(tabId, 10), onTabRemoved); + chrome.tabs.remove(tabId, resetLastError); }; /******************************************************************************/ vAPI.tabs.reload = function(tabId, bypassCache) { - if ( typeof tabId === 'string' ) { - tabId = parseInt(tabId, 10); - } - if ( isNaN(tabId) ) { - return; - } + if ( typeof tabId !== 'number' || tabId < 0 ) { return; } chrome.tabs.reload(tabId, { bypassCache: bypassCache === true }); }; @@ -357,15 +307,12 @@ vAPI.tabs.reload = function(tabId, bypassCache) { vAPI.tabs.injectScript = function(tabId, details, callback) { var onScriptExecuted = function() { - // https://code.google.com/p/chromium/issues/detail?id=410868#c8 - if ( chrome.runtime.lastError ) { - } + resetLastError(); if ( typeof callback === 'function' ) { callback(); } }; if ( tabId ) { - tabId = parseInt(tabId, 10); chrome.tabs.executeScript(tabId, details, onScriptExecuted); } else { chrome.tabs.executeScript(details, onScriptExecuted); @@ -399,8 +346,7 @@ vAPI.setIcon = (function() { }; return function(tabId, iconDetails, badgeDetails) { - tabId = parseInt(tabId, 10); - if ( isNaN(tabId) || tabId === -1 ) { return; } + if ( typeof tabId !== 'number' || tabId < 0 ) { return; } chrome.browserAction.setIcon( { tabId: tabId, path: iconDetails }, function() { onIconReady(tabId, badgeDetails); } @@ -572,7 +518,13 @@ vAPI.net.registerListeners = function() { ]); var normalizeRequestDetails = function(details) { - details.tabId = details.tabId.toString(); + if ( + details.tabId === -1 && + details.documentUrl === undefined && + details.initiator !== undefined + ) { + details.documentUrl = details.initiator; + } // The rest of the function code is to normalize request type if ( details.type !== 'other' ) { return; } @@ -762,31 +714,32 @@ vAPI.cloud = (function() { var maxChunkCountPerItem = Math.floor(512 * 0.75) & ~(chunkCountPerFetch - 1); // Mind chrome.storage.sync.QUOTA_BYTES_PER_ITEM (8192 at time of writing) - var maxChunkSize = chrome.storage.sync.QUOTA_BYTES_PER_ITEM || 8192; - - // Mind chrome.storage.sync.QUOTA_BYTES (128 kB at time of writing) - // Firefox: - // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage/sync - // > You can store up to 100KB of data using this API/ - var maxStorageSize = chrome.storage.sync.QUOTA_BYTES || 102400; - - // Flavor-specific handling needs to be done here. Reason: to allow time - // for vAPI.webextFlavor to be properly set. // https://github.com/gorhill/uBlock/issues/3006 // For Firefox, we will use a lower ratio to allow for more overhead for // the infrastructure. Unfortunately this leads to less usable space for // actual data, but all of this is provided for free by browser vendors, // so we need to accept and deal with these limitations. - var initialize = function() { - var ratio = - vAPI.webextFlavor === undefined || - vAPI.webextFlavor.startsWith('Mozilla-Firefox-') ? - 0.6 : - 0.75; - maxChunkSize = Math.floor(maxChunkSize * ratio); - initialize = function(){}; + var evalMaxChunkSize = function() { + return Math.floor( + (chrome.storage.sync.QUOTA_BYTES_PER_ITEM || 8192) * + (vAPI.webextFlavor.soup.has('firefox') ? 0.6 : 0.75) + ); }; + var maxChunkSize = evalMaxChunkSize(); + + // The real actual webextFlavor value may not be set in stone, so listen + // for possible future changes. + window.addEventListener('webextFlavor', function() { + maxChunkSize = evalMaxChunkSize(); + }, { once: true }); + + // Mind chrome.storage.sync.QUOTA_BYTES (128 kB at time of writing) + // Firefox: + // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/storage/sync + // > You can store up to 100KB of data using this API/ + var maxStorageSize = chrome.storage.sync.QUOTA_BYTES || 102400; + var options = { defaultDeviceName: window.navigator.platform, deviceName: vAPI.localStorage.getItem('deviceName') || '' @@ -843,7 +796,6 @@ vAPI.cloud = (function() { }; var push = function(dataKey, data, callback) { - initialize(); var bin = { 'source': options.deviceName || options.defaultDeviceName, @@ -882,7 +834,6 @@ vAPI.cloud = (function() { }; var pull = function(dataKey, callback) { - initialize(); var assembleChunks = function(bin) { if ( chrome.runtime.lastError ) { diff --git a/platform/chromium/vapi-client.js b/platform/chromium/vapi-client.js index 473a429..31faa0b 100644 --- a/platform/chromium/vapi-client.js +++ b/platform/chromium/vapi-client.js @@ -1,7 +1,7 @@ /******************************************************************************* uMatrix - a browser extension to block requests. - Copyright (C) 2014-2017 The uMatrix/uBlock Origin authors + Copyright (C) 2014-2018 The uMatrix/uBlock Origin authors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -47,7 +47,6 @@ vAPI.vapiClientInjected = true; vAPI.sessionId = String.fromCharCode(Date.now() % 26 + 97) + Math.random().toString(36).slice(2); -vAPI.chrome = true; /******************************************************************************/ diff --git a/platform/chromium/vapi-common.js b/platform/chromium/vapi-common.js index 3591c19..3a5f947 100644 --- a/platform/chromium/vapi-common.js +++ b/platform/chromium/vapi-common.js @@ -1,7 +1,7 @@ /******************************************************************************* uMatrix - a browser extension to block requests. - Copyright (C) 2014-2017 The uMatrix/uBlock Origin authors + Copyright (C) 2014-2018 The uMatrix/uBlock Origin authors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -40,6 +40,86 @@ var chrome = self.chrome; /******************************************************************************/ +vAPI.setTimeout = vAPI.setTimeout || window.setTimeout.bind(window); + +/******************************************************************************/ + +vAPI.webextFlavor = { + major: 0, + soup: new Set() +}; + +(function() { + var ua = navigator.userAgent, + flavor = vAPI.webextFlavor, + soup = flavor.soup; + var dispatch = function() { + window.dispatchEvent(new CustomEvent('webextFlavor')); + }; + + // This is always true. + soup.add('ublock'); + + if ( /\bMobile\b/.test(ua) ) { + soup.add('mobile'); + } + + // Asynchronous + var async = self.browser instanceof Object && + typeof self.browser.runtime.getBrowserInfo === 'function'; + if ( async ) { + self.browser.runtime.getBrowserInfo().then(function(info) { + flavor.major = parseInt(info.version, 10) || 0; + soup.add(info.vendor.toLowerCase()) + .add(info.name.toLowerCase()); + if ( flavor.major >= 53 ) { soup.add('user_stylesheet'); } + if ( flavor.major >= 57 ) { soup.add('html_filtering'); } + dispatch(); + }); + } + + // Synchronous + var match = /Firefox\/([\d.]+)/.exec(ua); + if ( match !== null ) { + flavor.major = parseInt(match[1], 10) || 0; + soup.add('mozilla') + .add('firefox'); + if ( flavor.major >= 53 ) { soup.add('user_stylesheet'); } + if ( flavor.major >= 57 ) { soup.add('html_filtering'); } + } else { + match = /OPR\/([\d.]+)/.exec(ua); + if ( match !== null ) { + var reEx = /Chrom(?:e|ium)\/([\d.]+)/; + if ( reEx.test(ua) ) { match = reEx.exec(ua); } + flavor.major = parseInt(match[1], 10) || 0; + soup.add('opera').add('chromium'); + } else { + match = /Chromium\/([\d.]+)/.exec(ua); + if ( match !== null ) { + flavor.major = parseInt(match[1], 10) || 0; + soup.add('chromium'); + } else { + match = /Chrome\/([\d.]+)/.exec(ua); + if ( match !== null ) { + flavor.major = parseInt(match[1], 10) || 0; + soup.add('google').add('chromium'); + } + } + } + // https://github.com/gorhill/uBlock/issues/3588 + if ( soup.has('chromium') && flavor.major >= 67 ) { + soup.add('user_stylesheet'); + } + } + + // Don't starve potential listeners + if ( !async ) { + vAPI.setTimeout(dispatch, 97); + } +})(); + +/******************************************************************************/ + // http://www.w3.org/International/questions/qa-scripts#directions var setScriptDirection = function(language) { @@ -121,10 +201,6 @@ vAPI.localStorage = { /******************************************************************************/ -vAPI.setTimeout = vAPI.setTimeout || window.setTimeout.bind(window); - -/******************************************************************************/ - })(this); /******************************************************************************/ diff --git a/src/js/background.js b/src/js/background.js index e07164e..e2a64fc 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -208,7 +208,8 @@ return { clearBrowserCacheCycle: 0, cspNoInlineScript: "script-src 'unsafe-eval' blob: *", cspNoInlineStyle: "style-src blob: *", - cspNoWorker: undefined, + cspNoWorker: "worker-src 'none'; report-uri about:blank", + cantMergeCSPHeaders: false, updateAssetsEvery: 11 * oneDay + 1 * oneHour + 1 * oneMinute + 1 * oneSecond, firstUpdateAfter: 11 * oneMinute, nextUpdateAfter: 11 * oneHour, @@ -220,9 +221,9 @@ return { // urls stats are kept on the back burner while waiting to be reactivated // in a tab or another. - pageStores: {}, + pageStores: new Map(), pageStoresToken: 0, - pageStoreCemetery: {}, + pageStoreCemetery: new Map(), // page url => permission scope tMatrix: null, diff --git a/src/js/contentscript-start.js b/src/js/contentscript-start.js index fccfe81..2a9b2e3 100644 --- a/src/js/contentscript-start.js +++ b/src/js/contentscript-start.js @@ -1,6 +1,6 @@ /******************************************************************************* - uMatrix - a Chromium browser extension to black/white list requests. + uMatrix - a browser extension to black/white list requests. Copyright (C) 2017-2018 Raymond Hill This program is free software: you can redistribute it and/or modify diff --git a/src/js/contentscript.js b/src/js/contentscript.js index c15731d..4d1f032 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -1,6 +1,6 @@ /******************************************************************************* - uMatrix - a Chromium browser extension to black/white list requests. + uMatrix - a browser extension to black/white list requests. Copyright (C) 2014-2018 Raymond Hill This program is free software: you can redistribute it and/or modify diff --git a/src/js/cookies.js b/src/js/cookies.js index 4d48407..6e4690e 100644 --- a/src/js/cookies.js +++ b/src/js/cookies.js @@ -509,10 +509,7 @@ vAPI.cookies.onChanged = (function() { queue.delete(qentry[0]); } if ( cookieKeys.length !== 0 ) { - let pageStores = µm.pageStores; - for ( let tabId in pageStores ) { - if ( pageStores.hasOwnProperty(tabId) === false ) { continue; } - let pageStore = pageStores[tabId]; + for ( let pageStore of µm.pageStores.values() ) { let allHostnamesString = pageStore.allHostnamesString; for ( let cookieKey of cookieKeys ) { if ( cookieMatchDomains(cookieKey, allHostnamesString) ) { diff --git a/src/js/logger-ui.js b/src/js/logger-ui.js index 41064fd..f72dccc 100644 --- a/src/js/logger-ui.js +++ b/src/js/logger-ui.js @@ -1,7 +1,7 @@ /******************************************************************************* uMatrix - a browser extension to benchmark browser session. - Copyright (C) 2015 Raymond Hill + Copyright (C) 2015-2018 Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -36,8 +36,8 @@ 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 pageStores = new Map(); +var pageStoresToken; var ownerId = Date.now(); var emphasizeTemplate = document.querySelector('#emphasizeTemplate > span'); @@ -71,7 +71,7 @@ var classNameFromTabId = function(tabId) { if ( tabId === noTabId ) { return 'tab_bts'; } - if ( tabId !== '' ) { + if ( tabId > 0 ) { return 'tab_' + tabId; } return ''; @@ -298,22 +298,17 @@ var logDate = new Date(), var renderLogEntries = function(response) { var entries = response.entries; - if ( entries.length === 0 ) { - return; - } + 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; - } + if ( entry.tab && pageStores.has(entry.tab) === false ) { continue; } renderLogEntry(entries[i]); } @@ -346,18 +341,14 @@ var renderLogEntries = function(response) { /******************************************************************************/ -var synchronizeTabIds = function(newTabIds) { - var oldTabIds = allTabIds; +var synchronizeTabIds = function(newPageStores) { + var oldPageStores = pageStores; 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; - } + for ( let entry of oldPageStores ) { + let tabId = entry[0]; + if ( newPageStores.has(tabId) ) { continue; } // Mark or remove voided rows trs = uDom('.tab_' + tabId); if ( autoDeleteVoidRows ) { @@ -374,22 +365,20 @@ var synchronizeTabIds = function(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 tabIds = Array.from(newPageStores.keys()).sort(function(a, b) { + return newPageStores.get(a).localeCompare(newPageStores.get(b)); }); var option; for ( var i = 0, j = 2; i < tabIds.length; i++ ) { - tabId = tabIds[i]; - if ( tabId === noTabId ) { - continue; - } + let 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.textContent = newPageStores.get(tabId); option.value = classNameFromTabId(tabId); if ( option.value === selectValue ) { option.setAttribute('selected', ''); @@ -407,7 +396,7 @@ var synchronizeTabIds = function(newTabIds) { pageSelectorChanged(); } - allTabIds = newTabIds; + pageStores = newPageStores; return rowVoided; }; @@ -446,9 +435,11 @@ var onLogBufferRead = function(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; + if ( response.pageStoresToken !== pageStoresToken ) { + if ( Array.isArray(response.pageStores) ) { + rowVoided = synchronizeTabIds(new Map(response.pageStores)); + } + pageStoresToken = response.pageStoresToken; } renderLogEntries(response); @@ -479,7 +470,11 @@ var readLogBuffer = function() { if ( ownerId === undefined ) { return; } vAPI.messaging.send( 'logger-ui.js', - { what: 'readMany', ownerId: ownerId }, + { + what: 'readMany', + ownerId: ownerId, + pageStoresToken: pageStoresToken + }, onLogBufferRead ); }; @@ -515,15 +510,11 @@ var pageSelectorChanged = function() { var refreshTab = function() { var tabClass = document.getElementById('pageSelector').value; var matches = tabClass.match(/^tab_(.+)$/); - if ( matches === null ) { - return; - } - if ( matches[1] === 'bts' ) { - return; - } + if ( matches === null ) { return; } + if ( matches[1] === 'bts' ) { return; } vAPI.messaging.send( 'logger-ui.js', - { what: 'forceReloadTab', tabId: matches[1] } + { what: 'forceReloadTab', tabId: parseInt(matches[1], 10) } ); }; diff --git a/src/js/messaging.js b/src/js/messaging.js index 8a09862..b8295f7 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -1,6 +1,6 @@ /******************************************************************************* - uMatrix - a Chromium browser extension to black/white list requests. + uMatrix - a browser extension to black/white list requests. Copyright (C) 2014-2018 Raymond Hill This program is free software: you can redistribute it and/or modify @@ -938,20 +938,23 @@ var onMessage = function(request, sender, callback) { 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; + let pageStores; + if ( request.pageStoresToken !== µm.pageStoresToken ) { + pageStores = []; + for ( let entry of µm.pageStores ) { + let tabId = entry[0]; + let pageStore = entry[1]; + if ( pageStore.rawUrl.startsWith(loggerURL) ) { continue; } + pageStores.push([ 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 + pageStores: pageStores, + pageStoresToken: µm.pageStoresToken }; break; diff --git a/src/js/popup.js b/src/js/popup.js index d661d2a..886277d 100644 --- a/src/js/popup.js +++ b/src/js/popup.js @@ -1,6 +1,6 @@ /******************************************************************************* - uMatrix - a Chromium browser extension to black/white list requests. + uMatrix - a browser extension to black/white list requests. Copyright (C) 2014-2018 Raymond Hill This program is free software: you can redistribute it and/or modify @@ -1584,13 +1584,13 @@ var matrixSnapshotPoller = (function() { }; (function() { - var tabId = matrixSnapshot.tabId; + let 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]; + tabId = parseInt(matches[1], 10); // No need for logger button when embedded in logger uDom('[data-extension-url="logger-ui.html"]').remove(); } diff --git a/src/js/start.js b/src/js/start.js index 010eddb..1d26205 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -1,7 +1,7 @@ /******************************************************************************* - uMatrix - a Chromium browser extension to black/white list requests. - Copyright (C) 2014-2017 Raymond Hill + uMatrix - a browser extension to black/white list requests. + Copyright (C) 2014-2018 Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -89,8 +89,10 @@ var onPSLReady = function() { // 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'); + let pageStore = + µm.pageStoreFactory(µm.tabContextManager.mustLookup(vAPI.noTabId)); + pageStore.title = vAPI.i18n('statsPageDetailedBehindTheScenePage'); + µm.pageStores.set(vAPI.noTabId, pageStore); vAPI.tabs.getAll(onTabsReady); }; diff --git a/src/js/storage.js b/src/js/storage.js index 55840d4..e709bf7 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -519,11 +519,32 @@ µ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 reLocalhost = new RegExp( + [ + '(?:^|\\s+)(?:', + [ + 'broadcasthost', + 'ip6-allnodes', + 'ip6-allrouters', + 'ip6-localhost', + 'ip6-loopback', + 'localhost\\.localdomain', + 'localhost', + 'local', + '0\\.0\\.0\\.0', + '127\\.0\\.0\\.1', + '255\\.255\\.255\\.255', + '::1', + 'ff02::1', + 'ff02::2', + 'fe80::1%lo0', + ].join('|'), + ')(?=\\s+|$)' + ].join(''), + 'g' + ); var reAsciiSegment = /^[\x21-\x7e]+$/; - var matches; var lineBeg = 0, lineEnd; - var line; while ( lineBeg < rawEnd ) { lineEnd = rawText.indexOf('\n', lineBeg); @@ -537,7 +558,7 @@ // rhill 2014-04-18: The trim is important here, as without it there // could be a lingering `\r` which would cause problems in the // following parsing code. - line = rawText.slice(lineBeg, lineEnd).trim(); + let line = rawText.slice(lineBeg, lineEnd).trim(); lineBeg = lineEnd + 1; // https://github.com/gorhill/httpswitchboard/issues/15 @@ -550,22 +571,15 @@ // The filter is whatever sequence of printable ascii character without // whitespaces - matches = reAsciiSegment.exec(line); - if ( !matches || matches.length === 0 ) { - continue; - } + let matches = reAsciiSegment.exec(line); + if ( matches === null ) { 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 ) { - continue; - } - + if ( matches[0] !== line ) { continue; } line = matches[0]; - if ( line === '' ) { - continue; - } + if ( line === '' ) { continue; } ubiquitousBlacklist.add(line); } diff --git a/src/js/tab.js b/src/js/tab.js index 48dac51..be62c1f 100644 --- a/src/js/tab.js +++ b/src/js/tab.js @@ -1,6 +1,6 @@ /******************************************************************************* - uMatrix - a Chromium browser extension to black/white list requests. + uMatrix - a browser extension to black/white list requests. Copyright (C) 2014-2018 Raymond Hill This program is free software: you can redistribute it and/or modify @@ -140,7 +140,7 @@ housekeep itself. */ µm.tabContextManager = (function() { - var tabContexts = Object.create(null); + var tabContexts = new Map(); // https://github.com/chrisaljoudi/uBlock/issues/1001 // This is to be used as last-resort fallback in case a tab is found to not @@ -170,18 +170,16 @@ housekeep itself. this.commitTimer = null; this.gcTimer = null; - tabContexts[tabId] = this; + tabContexts.set(tabId, this); }; TabContext.prototype.destroy = function() { - if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { - return; - } + if ( vAPI.isBehindTheSceneTabId(this.tabId) ) { return; } if ( this.gcTimer !== null ) { clearTimeout(this.gcTimer); this.gcTimer = null; } - delete tabContexts[this.tabId]; + tabContexts.delete(this.tabId); }; TabContext.prototype.onTab = function(tab) { @@ -285,7 +283,7 @@ housekeep itself. // These are to be used for the API of the tab context manager. var push = function(tabId, url, context) { - var entry = tabContexts[tabId]; + let entry = tabContexts.get(tabId); if ( entry === undefined ) { entry = new TabContext(tabId); entry.autodestroy(); @@ -303,7 +301,7 @@ housekeep itself. if ( url !== undefined ) { entry = push(tabId, url); } else { - entry = tabContexts[tabId]; + entry = tabContexts.get(tabId); } if ( entry !== undefined ) { return entry; @@ -329,11 +327,11 @@ housekeep itself. // about to fall through the cracks. // Example: Chromium + case #12 at // http://raymondhill.net/ublock/popup.html - return tabContexts[vAPI.noTabId]; + return tabContexts.get(vAPI.noTabId); }; var lookup = function(tabId) { - return tabContexts[tabId] || null; + return tabContexts.get(tabId) || null; }; // Behind-the-scene tab context @@ -372,7 +370,7 @@ housekeep itself. vAPI.tabs.onClosed = function(tabId) { µm.unbindTabFromPageStats(tabId); - var entry = tabContexts[tabId]; + let entry = tabContexts.get(tabId); if ( entry instanceof TabContext ) { entry.destroy(); } @@ -397,7 +395,7 @@ vAPI.tabs.registerListeners(); // Do not create a page store for URLs which are of no interests // Example: dev console - var tabContext = this.tabContextManager.lookup(tabId); + let tabContext = this.tabContextManager.lookup(tabId); if ( tabContext === null ) { throw new Error('Unmanaged tab id: ' + tabId); } @@ -406,14 +404,14 @@ vAPI.tabs.registerListeners(); // virtual tab. // https://github.com/gorhill/httpswitchboard/issues/67 if ( vAPI.isBehindTheSceneTabId(tabId) ) { - return this.pageStores[tabId]; + return this.pageStores.get(tabId); } - var normalURL = tabContext.normalURL; - var pageStore = this.pageStores[tabId] || null; + let normalURL = tabContext.normalURL; + let pageStore = this.pageStores.get(tabId); // The previous page URL, if any, associated with the tab - if ( pageStore !== null ) { + if ( pageStore !== undefined ) { // No change, do not rebind if ( pageStore.pageUrl === normalURL ) { return pageStore; @@ -425,7 +423,10 @@ vAPI.tabs.registerListeners(); // 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 ) { + if ( + context === 'updateURL' && + pageStore.pageHostname === tabContext.rootHostname + ) { pageStore.rawURL = tabContext.rawURL; pageStore.normalURL = normalURL; this.updateTitle(tabId); @@ -442,29 +443,22 @@ vAPI.tabs.registerListeners(); if ( pageStore === null ) { pageStore = this.pageStoreFactory(tabContext); } - this.pageStores[tabId] = pageStore; + this.pageStores.set(tabId, pageStore); this.updateTitle(tabId); this.pageStoresToken = Date.now(); - // 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; - } + if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } - var pageStore = this.pageStores[tabId] || null; - if ( pageStore === null ) { - return; - } + let pageStore = this.pageStores.get(tabId); + if ( pageStore === undefined ) { return; } - delete this.pageStores[tabId]; + this.pageStores.delete(tabId); this.pageStoresToken = Date.now(); if ( pageStore.incinerationTimer ) { @@ -472,13 +466,13 @@ vAPI.tabs.registerListeners(); pageStore.incinerationTimer = null; } - if ( this.pageStoreCemetery.hasOwnProperty(tabId) === false ) { - this.pageStoreCemetery[tabId] = {}; + let pageStoreCrypt = this.pageStoreCemetery.get(tabId); + if ( pageStoreCrypt === undefined ) { + this.pageStoreCemetery.set(tabId, (pageStoreCrypt = new Map())); } - var pageStoreCrypt = this.pageStoreCemetery[tabId]; - var pageURL = pageStore.pageUrl; - pageStoreCrypt[pageURL] = pageStore; + let pageURL = pageStore.pageUrl; + pageStoreCrypt.set(pageURL, pageStore); pageStore.incinerationTimer = vAPI.setTimeout( this.incineratePageStore.bind(this, tabId, pageURL), @@ -489,25 +483,21 @@ vAPI.tabs.registerListeners(); /******************************************************************************/ µm.resurrectPageStore = function(tabId, pageURL) { - if ( this.pageStoreCemetery.hasOwnProperty(tabId) === false ) { - return null; - } - var pageStoreCrypt = this.pageStoreCemetery[tabId]; + let pageStoreCrypt = this.pageStoreCemetery.get(tabId); + if ( pageStoreCrypt === undefined ) { return null; } - if ( pageStoreCrypt.hasOwnProperty(pageURL) === false ) { - return null; - } + let pageStore = pageStoreCrypt.get(pageURL); + if ( pageStore === undefined ) { return null; } - var pageStore = pageStoreCrypt[pageURL]; if ( pageStore.incinerationTimer !== null ) { clearTimeout(pageStore.incinerationTimer); pageStore.incinerationTimer = null; } - delete pageStoreCrypt[pageURL]; - if ( Object.keys(pageStoreCrypt).length === 0 ) { - delete this.pageStoreCemetery[tabId]; + pageStoreCrypt.delete(pageURL); + if ( pageStoreCrypt.size === 0 ) { + this.pageStoreCemetery.delete(tabId); } return pageStore; @@ -516,24 +506,20 @@ vAPI.tabs.registerListeners(); /******************************************************************************/ µm.incineratePageStore = function(tabId, pageURL) { - if ( this.pageStoreCemetery.hasOwnProperty(tabId) === false ) { - return; - } - var pageStoreCrypt = this.pageStoreCemetery[tabId]; + let pageStoreCrypt = this.pageStoreCemetery.get(tabId); + if ( pageStoreCrypt === undefined ) { return; } - if ( pageStoreCrypt.hasOwnProperty(pageURL) === false ) { - return; - } + let pageStore = pageStoreCrypt.get(pageURL); + if ( pageStore === undefined ) { return; } - var pageStore = pageStoreCrypt[pageURL]; if ( pageStore.incinerationTimer !== null ) { clearTimeout(pageStore.incinerationTimer); pageStore.incinerationTimer = null; } - delete pageStoreCrypt[pageURL]; - if ( Object.keys(pageStoreCrypt).length === 0 ) { - delete this.pageStoreCemetery[tabId]; + pageStoreCrypt.delete(pageURL); + if ( pageStoreCrypt.size === 0 ) { + this.pageStoreCemetery.delete(tabId); } pageStore.dispose(); @@ -542,12 +528,12 @@ vAPI.tabs.registerListeners(); /******************************************************************************/ µm.pageStoreFromTabId = function(tabId) { - return this.pageStores[tabId] || null; + return this.pageStores.get(tabId) || null; }; // Never return null µm.mustPageStoreFromTabId = function(tabId) { - return this.pageStores[tabId] || this.pageStores[vAPI.noTabId]; + return this.pageStores.get(tabId) || this.pageStores.get(vAPI.noTabId); }; /******************************************************************************/ @@ -608,25 +594,26 @@ vAPI.tabs.registerListeners(); /******************************************************************************/ µm.updateTitle = (function() { - var tabIdToTimer = Object.create(null); - var tabIdToTryCount = Object.create(null); - var delay = 499; + let tabIdToTimer = new Map(); + let tabIdToTryCount = new Map(); + let delay = 499; - var tryNoMore = function(tabId) { - delete tabIdToTryCount[tabId]; + let tryNoMore = function(tabId) { + tabIdToTryCount.delete(tabId); }; - var tryAgain = function(tabId) { - var count = tabIdToTryCount[tabId]; - if ( count === undefined ) { - return false; - } + let tryAgain = function(tabId) { + let count = tabIdToTryCount.get(tabId); + if ( count === undefined ) { return false; } if ( count === 1 ) { - delete tabIdToTryCount[tabId]; + tabIdToTryCount.delete(tabId); return false; } - tabIdToTryCount[tabId] = count - 1; - tabIdToTimer[tabId] = vAPI.setTimeout(updateTitle.bind(µm, tabId), delay); + tabIdToTryCount.set(tabId, count - 1); + tabIdToTimer.set( + tabId, + vAPI.setTimeout(updateTitle.bind(µm, tabId), delay) + ); return true; }; @@ -652,19 +639,21 @@ vAPI.tabs.registerListeners(); }; var updateTitle = function(tabId) { - delete tabIdToTimer[tabId]; + tabIdToTimer.delete(tabId); vAPI.tabs.get(tabId, onTabReady.bind(this, tabId)); }; return function(tabId) { - if ( vAPI.isBehindTheSceneTabId(tabId) ) { - return; - } - if ( tabIdToTimer[tabId] ) { - clearTimeout(tabIdToTimer[tabId]); + if ( vAPI.isBehindTheSceneTabId(tabId) ) { return; } + let timer = tabIdToTimer.get(tabId); + if ( timer !== undefined ) { + clearTimeout(timer); } - tabIdToTimer[tabId] = vAPI.setTimeout(updateTitle.bind(this, tabId), delay); - tabIdToTryCount[tabId] = 5; + tabIdToTimer.set( + tabId, + vAPI.setTimeout(updateTitle.bind(this, tabId), delay) + ); + tabIdToTryCount.set(tabId, 5); }; })(); @@ -680,7 +669,7 @@ vAPI.tabs.registerListeners(); var cleanup = function() { var vapiTabs = vAPI.tabs; - var tabIds = Object.keys(µm.pageStores).sort(); + var tabIds = Array.from(µm.pageStores.keys()).sort(); var checkTab = function(tabId) { vapiTabs.get(tabId, function(tab) { if ( !tab ) { @@ -695,9 +684,7 @@ vAPI.tabs.registerListeners(); var n = Math.min(cleanupSampleAt + cleanupSampleSize, tabIds.length); for ( var i = cleanupSampleAt; i < n; i++ ) { tabId = tabIds[i]; - if ( vAPI.isBehindTheSceneTabId(tabId) ) { - continue; - } + if ( vAPI.isBehindTheSceneTabId(tabId) ) { continue; } checkTab(tabId); } cleanupSampleAt = n; diff --git a/src/js/traffic.js b/src/js/traffic.js index cd737b7..fb33f50 100644 --- a/src/js/traffic.js +++ b/src/js/traffic.js @@ -127,6 +127,12 @@ var onBeforeRequestHandler = function(details) { rootHostname = tabContext.rootHostname, specificity = 0; + if ( tabId < 0 && details.documentUrl !== undefined ) { + rootHostname = µmuri.hostnameFromURI( + µm.normalizePageURL(0, details.documentUrl) + ); + } + // Filter through matrix var block = µm.tMatrix.mustBlock( rootHostname, @@ -344,16 +350,10 @@ var onHeadersReceived = function(details) { 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); + csp.push(µm.cspNoWorker); } else if ( µm.rawSettings.disableCSPReportInjection === false ) { - cspReport.push(cspNoWorker); + cspReport.push(µm.cspNoWorker); } if ( csp.length === 0 && cspReport.length === 0 ) { return; } @@ -368,7 +368,7 @@ var onHeadersReceived = function(details) { if ( csp.length !== 0 ) { let cspRight = csp.join(', '); let cspTotal = cspRight; - if ( cantMergeCSPHeaders ) { + if ( µm.cantMergeCSPHeaders ) { let i = headerIndexFromName( 'content-security-policy', headers @@ -390,7 +390,7 @@ var onHeadersReceived = function(details) { if ( cspReport.length !== 0 ) { let cspRight = cspReport.join(', '); let cspTotal = cspRight; - if ( cantMergeCSPHeaders ) { + if ( µm.cantMergeCSPHeaders ) { let i = headerIndexFromName( 'content-security-policy-report-only', headers @@ -411,34 +411,19 @@ var onHeadersReceived = function(details) { /******************************************************************************/ +// https://bugzilla.mozilla.org/show_bug.cgi?id=1302667 // https://github.com/gorhill/uMatrix/issues/967#issuecomment-373002011 -// This can be removed once Firefox 60 ESR is released. -var cantMergeCSPHeaders = (function() { - if ( - self.browser instanceof Object && - typeof self.browser.runtime.getBrowserInfo === 'function' - ) { - self.browser.runtime.getBrowserInfo().then(function(info) { - cantMergeCSPHeaders = - info.vendor === 'Mozilla' && - info.name === 'Firefox' && - parseInt(info.version, 10) < 59; - }); - } - return false; -})(); - -/******************************************************************************/ -var cspNoWorkerInit = function() { - if ( vAPI.webextFlavor === undefined ) { - return "child-src 'none'; frame-src data: blob: *; report-uri about:blank"; +window.addEventListener('webextFlavor', function() { + if ( vAPI.webextFlavor.soup.has('firefox') === false ) { return; } + if ( vAPI.webextFlavor.major <= 57 ) { + µMatrix.cspNoWorker = + "child-src 'none'; frame-src data: blob: *; report-uri about:blank"; } - µMatrix.cspNoWorker = /^Mozilla-Firefox-5[67]/.test(vAPI.webextFlavor) ? - "child-src 'none'; frame-src data: blob: *; report-uri about:blank" : - "worker-src 'none'; report-uri about:blank" ; - return µMatrix.cspNoWorker; -}; + if ( vAPI.webextFlavor.major <= 58 ) { + µMatrix.cantMergeCSPHeaders = true; + } +}, { once: true }); /******************************************************************************/