title bugs fixed, browserAction, badges

This commit is contained in:
Ilya Ig. Petrov 2016-02-12 01:07:39 +05:00
parent 9dd4724167
commit 046dfdbdec
4 changed files with 175 additions and 230 deletions

View File

@ -1,153 +1,116 @@
'use strict'; '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'); In what moment the title of the previous icon is cleared?
a.href = url; By my observations it usually takes place near tabs.onUpdate of tab status to "loading".
return a.hostname; 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.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (window.onTabUpdated[tabId]) {
chrome.pageAction.getTitle( window.onTabUpdated[tabId].map( f => f() );
{ tabId: requestDetails.tabId }, delete window.onTabUpdated[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();
}
)
}
var previousUpdateTitleFinished = Promise.resolve(); 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. function blockInform(requestDetails) {
Uncommenting logs reveals that START->FINISH pairs are messed. if (requestDetails.tabId !== -1 && window.antiCensorRu.pacProvider && window.antiCensorRu.pacProvider.proxyIps && window.antiCensorRu.pacProvider.proxyIps[ requestDetails.ip ]) {
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 ]) {
previousUpdateTitleFinished = previousUpdateTitleFinished.then( 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, blockInform,
{ urls: ['<all_urls>'] } { urls: ['<all_urls>'] }
); );
@ -155,4 +118,4 @@ chrome.webRequest.onCompleted.addListener(
chrome.webRequest.onErrorOccurred.addListener( chrome.webRequest.onErrorOccurred.addListener(
blockInform, blockInform,
{ urls: ['<all_urls>'] } { urls: ['<all_urls>'] }
); );

View File

@ -15,12 +15,13 @@
"webRequest", "webRequest",
"alarms", "alarms",
"storage", "storage",
"<all_urls>" "<all_urls>",
"tabs"
], ],
"background": { "background": {
"scripts": ["sync-pac-script-with-pac-provider.js", "block-informer.js"] "scripts": ["sync-pac-script-with-pac-provider.js", "block-informer.js"]
}, },
"page_action": { "browser_action": {
"default_popup": "/pages/choose-pac-provider/index.html" "default_popup": "/pages/choose-pac-provider/index.html"
}, },
"options_ui": { "options_ui": {

View File

@ -18,8 +18,10 @@ function renderPage() {
status.classList.add('off'); status.classList.add('off');
} }
console.log('ABC');
chrome.runtime.getBackgroundPage( backgroundPage => { chrome.runtime.getBackgroundPage( backgroundPage => {
backgroundPage.console.log('Options page opened.');
var antiCensorRu = backgroundPage.antiCensorRu; var antiCensorRu = backgroundPage.antiCensorRu;
// SET DATE // SET DATE

View File

@ -10,7 +10,6 @@
/* /*
In background scripts use window.antiCensorRu public variables. 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, In pages window.antiCensorRu are not accessible,
use chrome.runtime.getBackgroundPage(..), use chrome.runtime.getBackgroundPage(..),
avoid old extension.getBackgroundPage. avoid old extension.getBackgroundPage.
@ -96,45 +95,6 @@ window.antiCensorRu = {
cb(chrome.runtime.lastError, storage); 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) { syncWithPacProvider(cb) {
var cb = cb || (() => {}); var cb = cb || (() => {});
@ -179,15 +139,13 @@ window.antiCensorRu = {
var cb = asyncLogGroup('Installing PAC...', cb); var cb = asyncLogGroup('Installing PAC...', cb);
chrome.alarms.clear( chrome.alarms.create(
this._periodicUpdateAlarmReason, this._periodicUpdateAlarmReason,
() => chrome.alarms.create( { periodInMinutes: 4*60 }
this._periodicUpdateAlarmReason,
{ periodInMinutes: 4*60 }
)
); );
this.syncWithPacProvider(cb); return this.syncWithPacProvider(cb);
}, },
clearPac(cb) { clearPac(cb) {
@ -205,64 +163,86 @@ window.antiCensorRu = {
}; };
// ON EACH LAUNCH OF EXTENSION // ON EACH LAUNCH, STARTUP, RELOAD, UPDATE, ENABLE
// Not only on Chrome's startUp, but aslo on Enable/Disable chrome.storage.local.get(null, oldStorage => {
window.storageSyncedPromise = new Promise( console.log('Init on storage:', oldStorage);
(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 => {
// Finish each init with this callback setting alarm listeners.
function cb(err) {
chrome.alarms.onAlarm.addListener( chrome.alarms.onAlarm.addListener(
alarm => { alarm => {
if (alarm.name === window.antiCensorRu._periodicUpdateAlarmReason) { if (alarm.name === antiCensorRu._periodicUpdateAlarmReason) {
console.log('Periodic update triggered:', new Date()); console.log('Periodic update triggered:', new Date());
window.antiCensorRu.syncWithPacProvider(); antiCensorRu.syncWithPacProvider();
} }
} }
); );
console.log('Alarm listener installed. We won\'t miss any PAC update.'); console.log('Alarm listener installed. We won\'t miss any PAC update.');
chrome.alarms.get( chrome.alarms.get(
window.antiCensorRu._periodicUpdateAlarmReason, antiCensorRu._periodicUpdateAlarmReason,
alarm => alarm && console.log( alarm => {
'Next update is scheduled on', new Date(alarm.scheduledTime).toLocaleString('ru-RU') if (!alarm)
) return console.error('ALARM NOT SET');
); 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();
} }
);
}
// 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 // PRIVATE
@ -317,7 +297,7 @@ function updatePacProxyIps(provider, cb) {
(err, res) => { (err, res) => {
if (!err) { if (!err) {
provider.proxyIps = provider.proxyIps || {}; provider.proxyIps = provider.proxyIps || {};
provider.proxyIps[ JSON.parse(res).answer[0].rdata ] = proxyHost; provider.proxyIps[ JSON.parse(res).answer[0].rdata ] = proxyHost;
} else } else
failure.errors[proxyHost] = err; failure.errors[proxyHost] = err;
@ -355,7 +335,6 @@ function setPacScriptFromProvider(provider, cb) {
console.log('Setting chrome proxy settings...'); console.log('Setting chrome proxy settings...');
chrome.proxy.settings.set( {value: config}, cb ); chrome.proxy.settings.set( {value: config}, cb );
}); });
} }
); );
} }