diff --git a/extensions/chromium/minimalistic-pac-setter/extnesion/block-informer.js b/extensions/chromium/minimalistic-pac-setter/extnesion/block-informer.js index 81a3e5e..78a9316 100755 --- a/extensions/chromium/minimalistic-pac-setter/extnesion/block-informer.js +++ b/extensions/chromium/minimalistic-pac-setter/extnesion/block-informer.js @@ -1,153 +1,116 @@ 'use strict'; -// Shows user PageAction icon if any part of the current site is being blocked and proxied. +// Shows user browserAction icon if any part of the current site is being blocked and proxied. -function getHostname(url) { - var a = document.createElement('a'); - a.href = url; - return a.hostname; +/* + In what moment the title of the previous icon is cleared? + By my observations it usually takes place near tabs.onUpdate of tab status to "loading". + So if you set a title earlier it may be cleared by browser. + It pertains not only to page refesh but to newly opened pages too. + Also on loosing title see: + https://github.com/ilyaigpetrov/repository-for-chrome-bugs/blob/master/browserAction-title-lost-after-setting/background.js + Crazy concurrent Chrome. +**/ +window.onTabUpdated = {}; + +chrome.tabs.onRemoved.addListener( tabId => delete window.onTabUpdated[tabId] ); + +function afterTabUpdated(tabId, cb) { + if (window.onTabUpdated[tabId]) + window.onTabUpdated[tabId].push(cb); + else window.onTabUpdated[tabId] = [cb]; } -function updateTitle(requestDetails, cb) { - - chrome.pageAction.getTitle( - { tabId: requestDetails.tabId }, - title => { - - var ifTitleSetAlready = /\n/.test(title); - var proxyHost = window.antiCensorRu.pacProvider.proxyIps[ requestDetails.ip ]; - var hostname = getHostname(requestDetails.url); - var ifShouldUpdateTitle = false; - var indent = ' '; - var proxyTitle = 'Прокси:'; - - // Initially title equals extension name. - if (!ifTitleSetAlready) { - title = 'Разблокированы:\n'+ indent + hostname +'\n'+ proxyTitle +'\n'+ indent + proxyHost; - ifShouldUpdateTitle = true; - - function setIcon(iconOpts) { - iconOpts.tabId = requestDetails.tabId; - chrome.pageAction.setIcon(iconOpts); - chrome.pageAction.show(requestDetails.tabId); - } - var iconPath = '/icons/ribbon32.png'; - - if (requestDetails.type === 'main_frame') - setIcon({path: iconPath}); - else { - var canvas = document.createElement('canvas'); - var iconSize = 19; - canvas.width = canvas.height = iconSize; - var img = document.createElement('img'); - img.onload = () => { - var context = canvas.getContext('2d'); - context.drawImage(img, 0, 0, iconSize, iconSize); - var badgeText = '%'; - - context.fillStyle = 'red'; - context.fillRect(0, 9, context.measureText(badgeText).width + 1, iconSize); - - context.fillStyle = 'white'; - context.font = '11px Arial'; - context.fillText('%', 0, iconSize - 1); - - setIcon({imageData: context.getImageData(0, 0, iconSize, iconSize)}) - }; - img.src = iconPath; - } - } else { - var hostsProxiesPair = title.split(proxyTitle); - - if (hostsProxiesPair[1].indexOf(proxyHost) === -1) { - title = title.replace(hostsProxiesPair[1], hostsProxiesPair[1] +'\n'+ indent + proxyHost); - ifShouldUpdateTitle = true; - } - - if (hostsProxiesPair[0].indexOf(hostname) === -1) { - title = title.replace(proxyTitle, indent + hostname +'\n'+ proxyTitle); - ifShouldUpdateTitle = true; - } - } - - if (ifShouldUpdateTitle) - chrome.pageAction.setTitle({ - title: title, - tabId: requestDetails.tabId - }); - - return cb(); - } - ) -} +chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { + if (window.onTabUpdated[tabId]) { + window.onTabUpdated[tabId].map( f => f() ); + delete window.onTabUpdated[tabId]; + } +}); var previousUpdateTitleFinished = Promise.resolve(); -/* - previousUpdateTitleFinished works like a semaphor for updateTitle(). - What? Semaphor? In JavaScript? Are you kidding? - - Look at this: - - function getTitle(cb) { - chrome.pageAction.getTitle( - { tabId: details.tabId }, - cb - ); - } - - function setTitle(title) { - console.log(title); - chrome.pageAction.setTitle({ - title: title +' new!', - tabId: details.tabId - }); - chrome.pageAction.show(details.tabId); - } - - var updateTitle = (details, cb) => { - //var ID = parseInt(Math.random()*100); - //console.log(ID, 'START'); - getTitle( title => { - setTitle(title); - cb && cb(title); - //console.log(ID, 'FINISH'); - }); - } - updateTitle(details); - - Load some massive page. - The logs will be: - - FooBar Extension - (6) FooBar Extension new! - (2) FooBar Extension new! new! - FooBar Extension new! new! new! - FooBar Extension new! new! new! new! - Notice the (6) and (2) denoting the number of repeated log messages. - Uncommenting logs reveals that START->FINISH pairs are messed. - However, if getTitle is called strictly after setTitle, we are ok: - - previousUpdateTitleFinished = previousUpdateTitleFinished.then( - () => new Promise( resolve => updateTitle( details, resolve ) ) - ); - - Well, I hope it works, because once I caught a bug. -**/ - -function blockInform(details) { - - if (details.tabId !== -1 && window.antiCensorRu.pacProvider && window.antiCensorRu.pacProvider.proxyIps && window.antiCensorRu.pacProvider.proxyIps[ details.ip ]) { +function blockInform(requestDetails) { + if (requestDetails.tabId !== -1 && window.antiCensorRu.pacProvider && window.antiCensorRu.pacProvider.proxyIps && window.antiCensorRu.pacProvider.proxyIps[ requestDetails.ip ]) { previousUpdateTitleFinished = previousUpdateTitleFinished.then( - () => new Promise( resolve => updateTitle( details, resolve ) ) + () => new Promise( + resolve => { + var cb = () => updateTitle( requestDetails, resolve ); + return requestDetails.type === 'main_frame' ? afterTabUpdated(requestDetails.tabId, cb) : cb(); + } + ) ); + function updateTitle(requestDetails, cb) { + + chrome.browserAction.getTitle( + { tabId: requestDetails.tabId }, + title => { + var ifTitleSetAlready = /\n/.test(title); + var proxyHost = window.antiCensorRu.pacProvider.proxyIps[ requestDetails.ip ]; + + var a = document.createElement('a'); + a.href = requestDetails.url; + var hostname = a.hostname; + + var ifShouldUpdateTitle = false; + var indent = ' '; + var proxyTitle = 'Прокси:'; + + if (!ifTitleSetAlready) { + title = 'Разблокированы:\n'+ indent + hostname +'\n'+ proxyTitle +'\n'+ indent + proxyHost; + ifShouldUpdateTitle = true; + + chrome.browserAction.setBadgeText({ + tabId: requestDetails.tabId, + text: requestDetails.type === 'main_frame' ? '1' : '%1' + }); + + } else { + var hostsProxiesPair = title.split(proxyTitle); + + if (hostsProxiesPair[1].indexOf(proxyHost) === -1) { + title = title.replace(hostsProxiesPair[1], hostsProxiesPair[1] +'\n'+ indent + proxyHost); + ifShouldUpdateTitle = true; + } + + if (hostsProxiesPair[0].indexOf(hostname) === -1) { + title = title.replace(proxyTitle, indent + hostname +'\n'+ proxyTitle); + ifShouldUpdateTitle = true; + + var _cb = cb; + cb = () => chrome.browserAction.getBadgeText( + {tabId: requestDetails.tabId}, + result => { + chrome.browserAction.setBadgeText( + { + tabId: requestDetails.tabId, + text: ( isNaN( result.charAt(0) ) && result.charAt(0) || '' ) + (hostsProxiesPair[0].split('\n').length - 1) + } + ); + return _cb(); + } + ); + + } + } + + if (ifShouldUpdateTitle) + chrome.browserAction.setTitle({ + title: title, + tabId: requestDetails.tabId + }); + + return cb(); + } + ); + } + } - } -chrome.webRequest.onCompleted.addListener( +chrome.webRequest.onResponseStarted.addListener( blockInform, { urls: [''] } ); @@ -155,4 +118,4 @@ chrome.webRequest.onCompleted.addListener( chrome.webRequest.onErrorOccurred.addListener( blockInform, { urls: [''] } -); +); \ No newline at end of file diff --git a/extensions/chromium/minimalistic-pac-setter/extnesion/manifest.json b/extensions/chromium/minimalistic-pac-setter/extnesion/manifest.json index c0a4490..fd778bd 100755 --- a/extensions/chromium/minimalistic-pac-setter/extnesion/manifest.json +++ b/extensions/chromium/minimalistic-pac-setter/extnesion/manifest.json @@ -15,12 +15,13 @@ "webRequest", "alarms", "storage", - "" + "", + "tabs" ], "background": { "scripts": ["sync-pac-script-with-pac-provider.js", "block-informer.js"] }, - "page_action": { + "browser_action": { "default_popup": "/pages/choose-pac-provider/index.html" }, "options_ui": { diff --git a/extensions/chromium/minimalistic-pac-setter/extnesion/pages/choose-pac-provider/index.js b/extensions/chromium/minimalistic-pac-setter/extnesion/pages/choose-pac-provider/index.js index 334fee5..dbf208b 100755 --- a/extensions/chromium/minimalistic-pac-setter/extnesion/pages/choose-pac-provider/index.js +++ b/extensions/chromium/minimalistic-pac-setter/extnesion/pages/choose-pac-provider/index.js @@ -18,8 +18,10 @@ function renderPage() { status.classList.add('off'); } + console.log('ABC'); chrome.runtime.getBackgroundPage( backgroundPage => { + backgroundPage.console.log('Options page opened.'); var antiCensorRu = backgroundPage.antiCensorRu; // SET DATE diff --git a/extensions/chromium/minimalistic-pac-setter/extnesion/sync-pac-script-with-pac-provider.js b/extensions/chromium/minimalistic-pac-setter/extnesion/sync-pac-script-with-pac-provider.js index a8294ec..6ce4d44 100755 --- a/extensions/chromium/minimalistic-pac-setter/extnesion/sync-pac-script-with-pac-provider.js +++ b/extensions/chromium/minimalistic-pac-setter/extnesion/sync-pac-script-with-pac-provider.js @@ -10,7 +10,6 @@ /* In background scripts use window.antiCensorRu public variables. - Thay are synced with chrome.storage so they persist restarts. In pages window.antiCensorRu are not accessible, use chrome.runtime.getBackgroundPage(..), avoid old extension.getBackgroundPage. @@ -96,45 +95,6 @@ window.antiCensorRu = { cb(chrome.runtime.lastError, storage); }); }, - - migrateStorage(cb) { - chrome.storage.local.get(null, oldStorage => { - - var getPubCounter = version => parseInt( version.match(/\d+$/)[0] ); - var version = oldStorage.version || '0.0.0.0'; - var oldPubCounter = getPubCounter( version ); - - if (oldPubCounter > 9) { - console.log('No migration required, storage pub', oldPubCounter, 'is up to date.'); - return cb && cb(chrome.runtime.lastError, oldStorage); - } - - console.log('Starting storage migration from publication', oldPubCounter, 'to', getPubCounter( this.version )); - - this._currentPacProviderKey = oldStorage._currentPacProviderKey; - this.lastPacUpdateStamp = oldStorage.lastPacUpdateStamp; - // There is no need to persist other properties in the storage. - - return this.pushToStorage(cb); - - /* - - History of Changes to Storage - ----------------------------- - - Version 0.0.0.10 - - * Added this.version - * PacProvider.proxyIps changed from {ip -> Boolean} to {ip -> hostname} - - Version 0.0.0.8-9 - - * Changed storage.ifNotInstalled to storage.ifFirstInstall - * Added storage.lastPacUpdateStamp - - **/ - }); - }, syncWithPacProvider(cb) { var cb = cb || (() => {}); @@ -179,15 +139,13 @@ window.antiCensorRu = { var cb = asyncLogGroup('Installing PAC...', cb); - chrome.alarms.clear( + chrome.alarms.create( this._periodicUpdateAlarmReason, - () => chrome.alarms.create( - this._periodicUpdateAlarmReason, - { periodInMinutes: 4*60 } - ) + { periodInMinutes: 4*60 } ); - this.syncWithPacProvider(cb); + return this.syncWithPacProvider(cb); + }, clearPac(cb) { @@ -205,64 +163,86 @@ window.antiCensorRu = { }; -// ON EACH LAUNCH OF EXTENSION -// Not only on Chrome's startUp, but aslo on Enable/Disable +// ON EACH LAUNCH, STARTUP, RELOAD, UPDATE, ENABLE +chrome.storage.local.get(null, oldStorage => { -window.storageSyncedPromise = new Promise( - (resolve, reject) => { - /* We have to migrate on each launch, because: - 1. There is no way to check that chrome.runtime.onInstalled wasn't fired except timeout. - 2. There is no way to schedule onInstalled before pullFromStorage without timeouts. - 3. So inside onInstalled storage may only be already pulled. - */ - window.antiCensorRu.migrateStorage( - (err, migratedStorage) => - err ? reject(err) : window.antiCensorRu.pullFromStorage( - (err, res) => err ? reject(err) : resolve(res) - ) - ); - } -); - -window.storageSyncedPromise.then( - storage => { + console.log('Init on storage:', oldStorage); + // Finish each init with this callback setting alarm listeners. + function cb(err) { chrome.alarms.onAlarm.addListener( alarm => { - if (alarm.name === window.antiCensorRu._periodicUpdateAlarmReason) { + if (alarm.name === antiCensorRu._periodicUpdateAlarmReason) { console.log('Periodic update triggered:', new Date()); - window.antiCensorRu.syncWithPacProvider(); + antiCensorRu.syncWithPacProvider(); } } ); console.log('Alarm listener installed. We won\'t miss any PAC update.'); chrome.alarms.get( - window.antiCensorRu._periodicUpdateAlarmReason, - alarm => alarm && console.log( - 'Next update is scheduled on', new Date(alarm.scheduledTime).toLocaleString('ru-RU') - ) - ); - - } -); - -chrome.runtime.onInstalled.addListener( installDetails => { - console.log('Extension just installed, reason:', installDetails.reason); - window.storageSyncedPromise.then( - storage => { - - switch(installDetails.reason) { - case 'update': - console.log('Update or reload. Do nothing.'); - break; - case 'install': - window.antiCensorRu.ifFirstInstall = true; - chrome.runtime.openOptionsPage(); + antiCensorRu._periodicUpdateAlarmReason, + alarm => { + if (!alarm) + return console.error('ALARM NOT SET'); + console.log( + 'Next update is scheduled on', new Date(alarm.scheduledTime).toLocaleString('ru-RU') + ); } + ); + } + // INSTALL + antiCensorRu.ifFirstInstall = Object.keys(oldStorage).length === 0; + if (antiCensorRu.ifFirstInstall) { + console.log('Installing...'); + return chrome.runtime.openOptionsPage(cb); + } + + /* + 1. There is no way to check that chrome.runtime.onInstalled wasn't fired except timeout. + Otherwise we could put storage migration code only there. + 2. We have to check storage for migration before using it. + Better on each launch then on each pull. + */ + + // LAUNCH, RELOAD, UPDATE + + antiCensorRu._currentPacProviderKey = oldStorage._currentPacProviderKey || antiCensorRu._currentPacProviderKey; + antiCensorRu.lastPacUpdateStamp = oldStorage.lastPacUpdateStamp || antiCensorRu.lastPacUpdateStamp; + + if (antiCensorRu.version === oldStorage.version) { + // LAUNCH, RELOAD + console.log('Launch or reload. Do nothing.'); + return cb(); + } + + // UPDATE & MIGRATION + console.log('Updating...'); + return updatePacProxyIps( + antiCensorRu.pacProvider, + ipsError => { + if (ipsError) ipsError.ifNotCritical = true; + antiCensorRu.pushToStorage( pushError => cb( pushError || ipsError ) ); } - ) + ); + + /* + + History of Changes to Storage (Migration Guide) + ----------------------------------------------- + + Version 0.0.0.10 + + * Added this.version + * PacProvider.proxyIps changed from {ip -> Boolean} to {ip -> hostname} + + Version 0.0.0.8-9 + + * Changed storage.ifNotInstalled to storage.ifFirstInstall + * Added storage.lastPacUpdateStamp + + **/ }); // PRIVATE @@ -317,7 +297,7 @@ function updatePacProxyIps(provider, cb) { (err, res) => { if (!err) { provider.proxyIps = provider.proxyIps || {}; - provider.proxyIps[ JSON.parse(res).answer[0].rdata ] = proxyHost; + provider.proxyIps[ JSON.parse(res).answer[0].rdata ] = proxyHost; } else failure.errors[proxyHost] = err; @@ -355,7 +335,6 @@ function setPacScriptFromProvider(provider, cb) { console.log('Setting chrome proxy settings...'); chrome.proxy.settings.set( {value: config}, cb ); }); - } ); }