diff --git a/src/js/cookies.js b/src/js/cookies.js index 4762223..353786a 100644 --- a/src/js/cookies.js +++ b/src/js/cookies.js @@ -41,10 +41,10 @@ var µm = µMatrix; -var recordPageCookiesQueue = {}; -var removePageCookiesQueue = {}; -var removeCookieQueue = {}; -var cookieDict = {}; +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; @@ -54,10 +54,11 @@ var processPageRemoveQueueTimer = null; /******************************************************************************/ var CookieEntry = function(cookie) { - this.set(cookie); + this.usedOn = new Set(); + this.init(cookie); }; -CookieEntry.prototype.set = function(cookie) { +CookieEntry.prototype.init = function(cookie) { this.secure = cookie.secure; this.session = cookie.session; this.anySubdomain = cookie.domain.charAt(0) === '.'; @@ -67,36 +68,37 @@ CookieEntry.prototype.set = function(cookie) { this.name = cookie.name; this.value = cookie.value; this.tstamp = Date.now(); - this.usedOn = {}; + this.usedOn.clear(); return this; }; // Release anything which may consume too much memory -CookieEntry.prototype.unset = function() { +CookieEntry.prototype.dispose = function() { this.hostname = ''; this.domain = ''; this.path = ''; this.name = ''; this.value = ''; - this.usedOn = {}; + this.usedOn.clear(); return this; }; /******************************************************************************/ var addCookieToDict = function(cookie) { - var cookieKey = cookieKeyFromCookie(cookie); - if ( cookieDict.hasOwnProperty(cookieKey) === false ) { - var cookieEntry = cookieEntryJunkyard.pop(); + var cookieKey = cookieKeyFromCookie(cookie), + cookieEntry = cookieDict.get(cookieKey); + if ( cookieEntry === undefined ) { + cookieEntry = cookieEntryJunkyard.pop(); if ( cookieEntry ) { - cookieEntry.set(cookie); + cookieEntry.init(cookie); } else { cookieEntry = new CookieEntry(cookie); } - cookieDict[cookieKey] = cookieEntry; + cookieDict.set(cookieKey, cookieEntry); } - return cookieDict[cookieKey]; + return cookieEntry; }; /******************************************************************************/ @@ -111,13 +113,11 @@ var addCookiesToDict = function(cookies) { /******************************************************************************/ var removeCookieFromDict = function(cookieKey) { - if ( cookieDict.hasOwnProperty(cookieKey) === false ) { - return false; - } - var cookieEntry = cookieDict[cookieKey]; - delete cookieDict[cookieKey]; + var cookieEntry = cookieDict.get(cookieKey); + if ( cookieEntry === undefined ) { return false; } + cookieDict.delete(cookieKey); if ( cookieEntryJunkyard.length < 25 ) { - cookieEntryJunkyard.push(cookieEntry.unset()); + cookieEntryJunkyard.push(cookieEntry.dispose()); } return true; }; @@ -159,12 +159,6 @@ var cookieKeyFromCookieURL = function(url, type, name) { /******************************************************************************/ -var cookieEntryFromCookie = function(cookie) { - return cookieDict[cookieKeyFromCookie(cookie)]; -}; - -/******************************************************************************/ - var cookieURLFromCookieEntry = function(entry) { if ( !entry ) { return ''; @@ -175,10 +169,8 @@ var cookieURLFromCookieEntry = function(entry) { /******************************************************************************/ var cookieMatchDomains = function(cookieKey, allHostnamesString) { - var cookieEntry = cookieDict[cookieKey]; - if ( !cookieEntry ) { - return false; - } + var cookieEntry = cookieDict.get(cookieKey); + if ( cookieEntry === undefined ) { return false; } if ( allHostnamesString.indexOf(' ' + cookieEntry.hostname + ' ') < 0 ) { if ( !cookieEntry.anySubdomain ) { return false; @@ -202,7 +194,7 @@ var recordPageCookiesAsync = function(pageStats) { if ( !pageStats ) { return; } - recordPageCookiesQueue[pageStats.pageUrl] = pageStats; + recordPageCookiesQueue.set(pageStats.pageUrl, pageStats); if ( processPageRecordQueueTimer === null ) { processPageRecordQueueTimer = vAPI.setTimeout(processPageRecordQueue, 1000); } @@ -220,11 +212,9 @@ var cookieLogEntryBuilder = [ ]; var recordPageCookie = function(pageStore, cookieKey) { - if ( vAPI.isBehindTheSceneTabId(pageStore.tabId) ) { - return; - } + if ( vAPI.isBehindTheSceneTabId(pageStore.tabId) ) { return; } - var cookieEntry = cookieDict[cookieKey]; + var cookieEntry = cookieDict.get(cookieKey); var pageHostname = pageStore.pageHostname; var block = µm.mustBlock(pageHostname, cookieEntry.hostname, 'cookie'); @@ -240,7 +230,7 @@ var recordPageCookie = function(pageStore, cookieKey) { pageStore.recordRequest('cookie', cookieURL, block); µm.logger.writeOne(pageStore.tabId, 'net', pageHostname, cookieURL, 'cookie', block); - cookieEntry.usedOn[pageHostname] = true; + cookieEntry.usedOn.add(pageHostname); // rhill 2013-11-21: // https://github.com/gorhill/httpswitchboard/issues/65 @@ -267,7 +257,7 @@ var removePageCookiesAsync = function(pageStats) { if ( !pageStats ) { return; } - removePageCookiesQueue[pageStats.pageUrl] = pageStats; + removePageCookiesQueue.set(pageStats.pageUrl, pageStats); if ( processPageRemoveQueueTimer === null ) { processPageRemoveQueueTimer = vAPI.setTimeout(processPageRemoveQueue, 15 * 1000); } @@ -278,7 +268,7 @@ var removePageCookiesAsync = function(pageStats) { // Candidate for removal var removeCookieAsync = function(cookieKey) { - removeCookieQueue[cookieKey] = true; + removeCookieQueue.add(cookieKey); }; /******************************************************************************/ @@ -318,13 +308,10 @@ var i18nCookieDeleteFailure = vAPI.i18n('loggerEntryDeleteCookieError'); var processPageRecordQueue = function() { processPageRecordQueueTimer = null; - for ( var pageURL in recordPageCookiesQueue ) { - if ( !recordPageCookiesQueue.hasOwnProperty(pageURL) ) { - continue; - } - findAndRecordPageCookies(recordPageCookiesQueue[pageURL]); - delete recordPageCookiesQueue[pageURL]; + for ( var pageStore of recordPageCookiesQueue.values() ) { + findAndRecordPageCookies(pageStore); } + recordPageCookiesQueue.clear(); }; /******************************************************************************/ @@ -332,13 +319,10 @@ var processPageRecordQueue = function() { var processPageRemoveQueue = function() { processPageRemoveQueueTimer = null; - for ( var pageURL in removePageCookiesQueue ) { - if ( !removePageCookiesQueue.hasOwnProperty(pageURL) ) { - continue; - } - findAndRemovePageCookies(removePageCookiesQueue[pageURL]); - delete removePageCookiesQueue[pageURL]; + for ( var pageStore of removePageCookiesQueue.values() ) { + findAndRemovePageCookies(pageStore); } + removePageCookiesQueue.clear(); }; /******************************************************************************/ @@ -359,19 +343,12 @@ var processRemoveQueue = function() { var srcHostnames; var cookieEntry; - for ( var cookieKey in removeCookieQueue ) { - if ( removeCookieQueue.hasOwnProperty(cookieKey) === false ) { - continue; - } - delete removeCookieQueue[cookieKey]; - + 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[cookieKey]; - if ( !cookieEntry ) { - continue; - } + cookieEntry = cookieDict.get(cookieKey); + if ( cookieEntry === undefined ) { continue; } // Delete obsolete session cookies: enabled. if ( tstampObsolete !== 0 && cookieEntry.session ) { @@ -399,6 +376,8 @@ var processRemoveQueue = function() { } } + removeCookieQueue.clear(); + vAPI.setTimeout(processRemoveQueue, processRemoveQueuePeriod); }; @@ -407,16 +386,29 @@ var processRemoveQueue = function() { // 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() { - // 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 cookieKeys = Object.keys(cookieDict); - if ( cookieKeys.length > 25 ) { - cookieKeys = cookieKeys.sort(function(){return Math.random() < 0.5;}).splice(0, 50); - } - while ( cookieKeys.length ) { - removeCookieAsync(cookieKeys.pop()); + 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); @@ -425,45 +417,33 @@ var processClean = function() { /******************************************************************************/ var findAndRecordPageCookies = function(pageStore) { - for ( var cookieKey in cookieDict ) { - if ( !cookieDict.hasOwnProperty(cookieKey) ) { - continue; - } - if ( cookieMatchDomains(cookieKey, pageStore.allHostnamesString) === false ) { - continue; + for ( var cookieKey of cookieDict.keys() ) { + if ( cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) { + recordPageCookie(pageStore, cookieKey); } - recordPageCookie(pageStore, cookieKey); } }; /******************************************************************************/ var findAndRemovePageCookies = function(pageStore) { - for ( var cookieKey in cookieDict ) { - if ( !cookieDict.hasOwnProperty(cookieKey) ) { - continue; + for ( var cookieKey of cookieDict.keys() ) { + if ( cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) { + removeCookieAsync(cookieKey); } - if ( !cookieMatchDomains(cookieKey, pageStore.allHostnamesString) ) { - continue; - } - removeCookieAsync(cookieKey); } }; /******************************************************************************/ var canRemoveCookie = function(cookieKey, srcHostnames) { - var cookieEntry = cookieDict[cookieKey]; - if ( !cookieEntry ) { - return false; - } + var cookieEntry = cookieDict.get(cookieKey); + if ( cookieEntry === undefined ) { return false; } + var cookieHostname = cookieEntry.hostname; var srcHostname; - for ( srcHostname in cookieEntry.usedOn ) { - if ( cookieEntry.usedOn.hasOwnProperty(srcHostname) === false ) { - continue; - } + for ( srcHostname of cookieEntry.usedOn ) { if ( µm.mustAllow(srcHostname, cookieHostname, 'cookie') ) { return false; } @@ -500,14 +480,12 @@ 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[cookieKey]; - if ( !cookieEntry ) { + var cookieEntry = cookieDict.get(cookieKey); + if ( cookieEntry === undefined ) { cookieEntry = addCookieToDict(cookie); } else { cookieEntry.tstamp = Date.now(); - if ( cookie.value === cookieEntry.value ) { - return; - } + if ( cookie.value === cookieEntry.value ) { return; } cookieEntry.value = cookie.value; } @@ -543,8 +521,8 @@ vAPI.cookies.onRemoved = function(cookie) { // Listen to any change in cookieland, we will update page stats accordingly. vAPI.cookies.onAllRemoved = function() { - for ( var cookieKey in cookieDict ) { - if ( cookieDict.hasOwnProperty(cookieKey) && removeCookieFromDict(cookieKey) ) { + for ( var cookieKey of cookieDict.keys() ) { + if ( removeCookieFromDict(cookieKey) ) { µm.logger.writeOne('', 'info', 'cookie', i18nCookieDeleteSuccess.replace('{{value}}', cookieKey)); } }