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,61 +1,72 @@
'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]) {
window.onTabUpdated[tabId].map( f => f() );
delete window.onTabUpdated[tabId];
}
});
chrome.pageAction.getTitle( var previousUpdateTitleFinished = Promise.resolve();
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 => {
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 }, { tabId: requestDetails.tabId },
title => { title => {
var ifTitleSetAlready = /\n/.test(title); var ifTitleSetAlready = /\n/.test(title);
var proxyHost = window.antiCensorRu.pacProvider.proxyIps[ requestDetails.ip ]; var proxyHost = window.antiCensorRu.pacProvider.proxyIps[ requestDetails.ip ];
var hostname = getHostname(requestDetails.url);
var a = document.createElement('a');
a.href = requestDetails.url;
var hostname = a.hostname;
var ifShouldUpdateTitle = false; var ifShouldUpdateTitle = false;
var indent = ' '; var indent = ' ';
var proxyTitle = 'Прокси:'; var proxyTitle = 'Прокси:';
// Initially title equals extension name.
if (!ifTitleSetAlready) { if (!ifTitleSetAlready) {
title = 'Разблокированы:\n'+ indent + hostname +'\n'+ proxyTitle +'\n'+ indent + proxyHost; title = 'Разблокированы:\n'+ indent + hostname +'\n'+ proxyTitle +'\n'+ indent + proxyHost;
ifShouldUpdateTitle = true; ifShouldUpdateTitle = true;
function setIcon(iconOpts) { chrome.browserAction.setBadgeText({
iconOpts.tabId = requestDetails.tabId; tabId: requestDetails.tabId,
chrome.pageAction.setIcon(iconOpts); text: requestDetails.type === 'main_frame' ? '1' : '%1'
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 { } else {
var hostsProxiesPair = title.split(proxyTitle); var hostsProxiesPair = title.split(proxyTitle);
@ -67,87 +78,39 @@ function updateTitle(requestDetails, cb) {
if (hostsProxiesPair[0].indexOf(hostname) === -1) { if (hostsProxiesPair[0].indexOf(hostname) === -1) {
title = title.replace(proxyTitle, indent + hostname +'\n'+ proxyTitle); title = title.replace(proxyTitle, indent + hostname +'\n'+ proxyTitle);
ifShouldUpdateTitle = true; 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) if (ifShouldUpdateTitle)
chrome.pageAction.setTitle({ chrome.browserAction.setTitle({
title: title, title: title,
tabId: requestDetails.tabId tabId: requestDetails.tabId
}); });
return cb(); return cb();
} }
) );
}
}
} }
var previousUpdateTitleFinished = Promise.resolve(); chrome.webRequest.onResponseStarted.addListener(
/*
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 ]) {
previousUpdateTitleFinished = previousUpdateTitleFinished.then(
() => new Promise( resolve => updateTitle( details, resolve ) )
);
}
}
chrome.webRequest.onCompleted.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.
@ -97,45 +96,6 @@ window.antiCensorRu = {
}); });
}, },
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,
() => chrome.alarms.create(
this._periodicUpdateAlarmReason, this._periodicUpdateAlarmReason,
{ periodInMinutes: 4*60 } { 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 => {
if (!alarm)
return console.error('ALARM NOT SET');
console.log(
'Next update is scheduled on', new Date(alarm.scheduledTime).toLocaleString('ru-RU') '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 ) );
}
); );
} /*
);
chrome.runtime.onInstalled.addListener( installDetails => { History of Changes to Storage (Migration Guide)
console.log('Extension just installed, reason:', installDetails.reason); -----------------------------------------------
window.storageSyncedPromise.then(
storage => {
switch(installDetails.reason) { Version 0.0.0.10
case 'update':
console.log('Update or reload. Do nothing.');
break;
case 'install':
window.antiCensorRu.ifFirstInstall = true;
chrome.runtime.openOptionsPage();
}
} * 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
@ -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 );
}); });
} }
); );
} }