first pass

pull/2/head
gorhill 10 years ago
parent a5d051de3f
commit ed67045360

Before

Width:  |  Height:  |  Size: 777 B

After

Width:  |  Height:  |  Size: 777 B

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -10,7 +10,7 @@
},
"browser_action": {
"default_icon": {
"19": "img/browsericons/icon19.png"
"19": "img/browsericons/icon19-off.png"
},
"default_title": "__MSG_extName__",
"default_popup": "popup.html"
@ -52,7 +52,7 @@
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["js/contentscript-start.js"],
"js": ["js/vapi-client.js", "js/contentscript-start.js"],
"run_at": "document_start",
"all_frames": true
},

@ -1,7 +1,7 @@
/*******************************************************************************
µBlock - a browser extension to block requests.
Copyright (C) 2014 The µBlock authors
µMatrix - a browser extension to block requests.
Copyright (C) 2014 The uBlock 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
@ -19,7 +19,7 @@
Home: https://github.com/gorhill/uBlock
*/
/* global self, µBlock */
/* global self, µMatrix */
// For background page
@ -49,6 +49,34 @@ vAPI.app = {
/******************************************************************************/
vAPI.app.start = function() {
// rhill 2013-12-07:
// Relinquish control over javascript execution to the user.
// https://github.com/gorhill/httpswitchboard/issues/74
chrome.contentSettings.javascript.clear({});
};
/******************************************************************************/
vAPI.app.stop = function() {
chrome.contentSettings.javascript.clear({});
// rhill 2013-12-07:
// Tell Chromium to allow all javascript: µMatrix will control whether
// javascript execute through `Content-Policy-Directive` and webRequest.
// https://github.com/gorhill/httpswitchboard/issues/74
chrome.contentSettings.javascript.set({
primaryPattern: 'https://*/*',
setting: 'allow'
});
chrome.contentSettings.javascript.set({
primaryPattern: 'http://*/*',
setting: 'allow'
});
};
/******************************************************************************/
vAPI.app.restart = function() {
chrome.runtime.reload();
};
@ -114,7 +142,8 @@ vAPI.tabs.registerListeners = function() {
if ( popup !== undefined ) {
return;
}
return popupCandidates[details.tabId] = new PopupCandidate(details);
popup = popupCandidates[details.tabId] = new PopupCandidate(details);
return popup;
};
var popupCandidateTest = function(details) {
@ -190,11 +219,12 @@ vAPI.tabs.registerListeners = function() {
if ( typeof this.onClosed === 'function' ) {
chrome.tabs.onRemoved.addListener(this.onClosed);
}
};
/******************************************************************************/
// tabId: null, // active tab
vAPI.tabs.get = function(tabId, callback) {
var onTabReady = function(tab) {
// https://code.google.com/p/chromium/issues/detail?id=410868#c8
@ -223,6 +253,12 @@ vAPI.tabs.get = function(tabId, callback) {
/******************************************************************************/
vAPI.tabs.getAll = function(callback) {
chrome.tabs.query({ url: '<all_urls>' }, callback);
};
/******************************************************************************/
// properties of the details object:
// url: 'URL', // the address that will be opened
// tabId: 1, // the tab is used if set, instead of creating a new one
@ -356,6 +392,9 @@ vAPI.tabs.reload = function(tabId /*, flags*/) {
if ( typeof tabId === 'string' ) {
tabId = parseInt(tabId, 10);
}
if ( isNaN(tabId) ) {
return;
}
chrome.tabs.reload(tabId);
};
@ -387,8 +426,11 @@ vAPI.tabs.injectScript = function(tabId, details, callback) {
// Since we may be called asynchronously, the tab id may not exist
// anymore, so this ensures it does still exist.
vAPI.setIcon = function(tabId, iconStatus, badge) {
vAPI.setIcon = function(tabId, iconId, badge) {
tabId = parseInt(tabId, 10);
if ( isNaN(tabId) || tabId <= 0 ) {
return;
}
var onIconReady = function() {
if ( vAPI.lastError() ) {
return;
@ -397,14 +439,16 @@ vAPI.setIcon = function(tabId, iconStatus, badge) {
if ( badge !== '' ) {
chrome.browserAction.setBadgeBackgroundColor({
tabId: tabId,
color: '#666'
color: '#000'
});
}
};
var iconPaths = iconStatus === 'on' ?
{ '19': 'img/browsericons/icon19.png', '38': 'img/browsericons/icon38.png' } :
{ '19': 'img/browsericons/icon19-off.png', '38': 'img/browsericons/icon38-off.png' };
var iconSelector = typeof iconId === 'number' ? iconId : 'off';
var iconPaths = {
'19': 'img/browsericons/icon19-' + iconSelector + '.png'/* ,
'38': 'img/browsericons/icon38-' + iconSelector + '.png' */
};
chrome.browserAction.setIcon({ tabId: tabId, path: iconPaths }, onIconReady);
};
@ -449,7 +493,7 @@ vAPI.messaging.onPortMessage = function(request, port) {
return;
}
console.error(Block> messaging > unknown request: %o', request);
console.error(Matrix> messaging > unknown request: %o', request);
// Unhandled:
// Need to callback anyways in case caller expected an answer, or
@ -568,21 +612,21 @@ vAPI.net = {};
/******************************************************************************/
vAPI.net.registerListeners = function() {
var µb = µBlock;
var µburi = µb.URI;
var µm = µMatrix;
var µmuri = µm.URI;
var normalizeRequestDetails = function(details) {
µburi.set(details.url);
µmuri.set(details.url);
details.tabId = details.tabId.toString();
details.hostname = µburi.hostnameFromURI(details.url);
details.hostname = µmuri.hostnameFromURI(details.url);
// The rest of the function code is to normalize type
if ( details.type !== 'other' ) {
return;
}
var tail = µburi.path.slice(-6);
var tail = µmuri.path.slice(-6);
var pos = tail.lastIndexOf('.');
// https://github.com/chrisaljoudi/uBlock/issues/862
@ -628,6 +672,20 @@ vAPI.net.registerListeners = function() {
this.onBeforeRequest.extra
);
var onBeforeSendHeadersClient = this.onBeforeSendHeaders.callback;
var onBeforeSendHeaders = function(details) {
normalizeRequestDetails(details);
return onBeforeSendHeadersClient(details);
};
chrome.webRequest.onBeforeSendHeaders.addListener(
onBeforeSendHeaders,
{
'urls': this.onBeforeSendHeaders.urls || ['<all_urls>'],
'types': this.onBeforeSendHeaders.types || []
},
this.onBeforeSendHeaders.extra
);
var onHeadersReceivedClient = this.onHeadersReceived.callback;
var onHeadersReceived = function(details) {
normalizeRequestDetails(details);
@ -641,6 +699,13 @@ vAPI.net.registerListeners = function() {
},
this.onHeadersReceived.extra
);
chrome.webRequest.onErrorOccurred.addListener(
this.onErrorOccurred.callback,
{
'urls': this.onErrorOccurred.urls || ['<all_urls>']
}
);
};
/******************************************************************************/
@ -672,48 +737,6 @@ vAPI.lastError = function() {
// the web pages before uBlock was ready.
vAPI.onLoadAllCompleted = function() {
// http://code.google.com/p/chromium/issues/detail?id=410868#c11
// Need to be sure to access `vAPI.lastError()` to prevent
// spurious warnings in the console.
var scriptDone = function() {
vAPI.lastError();
};
var scriptEnd = function(tabId) {
if ( vAPI.lastError() ) {
return;
}
vAPI.tabs.injectScript(tabId, {
file: 'js/contentscript-end.js',
allFrames: true,
runAt: 'document_idle'
}, scriptDone);
};
var scriptStart = function(tabId) {
vAPI.tabs.injectScript(tabId, {
file: 'js/vapi-client.js',
allFrames: true,
runAt: 'document_start'
}, function(){ });
vAPI.tabs.injectScript(tabId, {
file: 'js/contentscript-start.js',
allFrames: true,
runAt: 'document_start'
}, function(){ scriptEnd(tabId); });
};
var bindToTabs = function(tabs) {
var µb = µBlock;
var i = tabs.length, tab;
while ( i-- ) {
tab = tabs[i];
µb.tabContextManager.commit(tab.id, tab.url);
µb.bindTabToPageStats(tab.id);
// https://github.com/chrisaljoudi/uBlock/issues/129
scriptStart(tab.id);
}
};
chrome.tabs.query({ url: 'http://*/*' }, bindToTabs);
chrome.tabs.query({ url: 'https://*/*' }, bindToTabs);
};
/******************************************************************************/
@ -728,6 +751,43 @@ vAPI.punycodeURL = function(url) {
/******************************************************************************/
vAPI.browserCache = {};
/******************************************************************************/
vAPI.browserCache.clearByTime = function(since) {
chrome.browsingData.removeCache({ since: 0 });
};
vAPI.browserCache.clearByOrigin = function(/* domain */) {
// unsupported on Chromium
};
/******************************************************************************/
vAPI.cookies = {};
/******************************************************************************/
vAPI.cookies.registerListeners = function() {
if ( typeof this.onChanged === 'function' ) {
chrome.cookies.onChanged.addListener(this.onChanged);
}
};
/******************************************************************************/
vAPI.cookies.getAll = function(callback) {
chrome.cookies.getAll({}, callback);
};
/******************************************************************************/
vAPI.cookies.remove = function(details, callback) {
chrome.cookies.remove(details, callback || noopFunc);
};
/******************************************************************************/
})();
/******************************************************************************/

@ -38,8 +38,8 @@ if ( vAPI.vapiClientInjected ) {
//console.debug('vapi-client.js already injected: skipping.');
return;
}
vAPI.vapiClientInjected = true;
vAPI.sessionId = String.fromCharCode(Date.now() % 25 + 97) +
Math.random().toString(36).slice(2);
vAPI.chrome = true;

@ -1,6 +1,6 @@
/*******************************************************************************
µBlock - a browser extension to block requests.
µMatrix - a browser extension to block requests.
Copyright (C) 2014 The µBlock authors
This program is free software: you can redistribute it and/or modify
@ -16,7 +16,7 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
Home: https://github.com/gorhill/uMatrix
*/
// For background page or non-background pages

@ -47,10 +47,11 @@ ul {
<span style="display: none;" data-i18n="aboutResetConfirm"></span>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/dashboard-common.js"></script>
<script src="js/messaging-client.js"></script>
<script src="js/about.js"></script>
</body>

@ -12,8 +12,8 @@
</head>
<body>
<div id="content"></div>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/messaging-client.js"></script>
<script src="js/asset-viewer.js"></script>
</body>
</html>

@ -8,6 +8,8 @@
<script src="lib/punycode.min.js"></script>
<script src="lib/publicsuffixlist.min.js"></script>
<script src="lib/yamd5.min.js"></script>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-background.js"></script>
<script src="js/types.js"></script>
<script src="js/background.js"></script>
<script src="js/xal.js"></script>
@ -25,11 +27,10 @@
<script src="js/profiler.js"></script>
<script src="js/storage.js"></script>
<script src="js/pagestats.js"></script>
<script src="js/tab.js"></script>
<script src="js/uritools.js"></script>
<script src="js/tab.js"></script>
<script src="js/traffic.js"></script>
<script src="js/useragent.js"></script>
<script src="js/messaging-handlers.js"></script>
<script src="js/start.js"></script>
<script src="js/commands.js"></script>
</body>

@ -90,6 +90,8 @@ iframe {
<iframe src=""></iframe>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/dashboard.js"></script>

@ -29,10 +29,11 @@
<div id="busyOverlay"></div>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/dashboard-common.js"></script>
<script src="js/messaging-client.js"></script>
<script src="js/hosts-files.js"></script>
</body>

@ -109,10 +109,11 @@
</div> <!-- end of detailed stats -->
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/dashboard-common.js"></script>
<script src="js/messaging-client.js"></script>
<script src="js/info.js"></script>
</body>
</html>

@ -19,24 +19,29 @@
Home: https://github.com/gorhill/uMatrix
*/
/* global chrome, messaging, uDom */
/* global vAPI, uDom */
/******************************************************************************/
uDom.onLoad(function() {
'use strict';
/******************************************************************************/
var messager = vAPI.messaging.channel('about.js');
/******************************************************************************/
var backupUserDataToFile = function() {
var userDataReady = function(userData) {
chrome.downloads.download({
vAPI.download({
'url': 'data:text/plain,' + encodeURIComponent(JSON.stringify(userData)),
'filename': uDom('[data-i18n="aboutBackupFilename"]').text(),
'saveAs': true
'filename': uDom('[data-i18n="aboutBackupFilename"]').text()
});
};
messaging.ask({ what: 'getAllUserData' }, userDataReady);
messager.send({ what: 'getAllUserData' }, userDataReady);
};
/******************************************************************************/
@ -75,7 +80,7 @@ function restoreUserDataFromFile() {
.replace('{{time}}', time.toLocaleString());
var proceed = window.confirm(msg);
if ( proceed ) {
messaging.tell({ what: 'restoreAllUserData', userData: userData });
messager.send({ what: 'restoreAllUserData', userData: userData });
}
};
@ -107,7 +112,7 @@ var startRestoreFilePicker = function() {
var resetUserData = function() {
var proceed = window.confirm(uDom('[data-i18n="aboutResetConfirm"]').text());
if ( proceed ) {
messaging.tell({ what: 'resetAllUserData' });
messager.send({ what: 'resetAllUserData' });
}
};
@ -123,8 +128,7 @@ var resetUserData = function() {
}
uDom('#aboutStorageUsed').html(template.replace('{{storageUsed}}', storageUsed));
};
messaging.start('about.js');
messaging.ask({ what: 'getSomeStats' }, renderStats);
messager.send({ what: 'getSomeStats' }, renderStats);
})();
/******************************************************************************/

@ -19,15 +19,17 @@
Home: https://github.com/gorhill/uMatrix
*/
/* global chrome, messaging, uDom */
/* global vAPI, uDom */
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
messaging.start('asset-viewer.js');
var messager = vAPI.messaging.channel('asset-viewer.js');
/******************************************************************************/
@ -43,7 +45,7 @@ if ( !matches || matches.length !== 2 ) {
return;
}
messaging.ask({ what : 'getAssetContent', url: matches[1] }, onAssetContentReceived);
messager.send({ what : 'getAssetContent', url: matches[1] }, onAssetContentReceived);
/******************************************************************************/

File diff suppressed because it is too large Load Diff

@ -145,11 +145,7 @@ return asyncJobManager;
pageStore.updateBadge(tabId);
return;
}
µm.XAL.setIcon(
tabId,
{ '19': 'img/browsericons/icon19.png' },
'?'
);
vAPI.setIcon(tabId, null, '?');
};
var updateBadgeAsync = function(tabId) {
@ -172,7 +168,7 @@ return asyncJobManager;
// does not exist. I suspect this could be related to
// https://github.com/gorhill/httpswitchboard/issues/58
var urlStatsChangedCallback = function(pageUrl) {
µMatrix.messaging.tell('popup.js', {
vAPI.messaging.broadcast({
what: 'urlStatsChanged',
pageURL: pageUrl
});

@ -49,8 +49,6 @@ var defaultUserAgentStrings = [
/******************************************************************************/
return {
manifest: chrome.runtime.getManifest(),
userSettings: {
autoUpdate: false,
clearBrowserCache: true,
@ -125,7 +123,6 @@ return {
refererHeaderFoiledCounter: 0,
hyperlinkAuditingFoiledCounter: 0,
browserCacheClearedCounter: 0,
storageQuota: chrome.storage.local.QUOTA_BYTES,
storageUsed: 0,
userAgentReplaceStr: '',
userAgentReplaceStrBirth: 0,
@ -138,8 +135,8 @@ return {
// Commonly encountered strings
chromeExtensionURLPrefix: 'chrome-extension://',
noopCSSURL: chrome.runtime.getURL('css/noop.css'),
fontCSSURL: chrome.runtime.getURL('css/fonts/Roboto_Condensed/RobotoCondensed-Regular.ttf'),
noopCSSURL: vAPI.getURL('css/noop.css'),
fontCSSURL: vAPI.getURL('css/fonts/Roboto_Condensed/RobotoCondensed-Regular.ttf'),
noopFunc: function(){},

@ -47,7 +47,7 @@ var onCommand = function(command) {
µMatrix.revertAllRules();
break;
case 'whitelist-all':
chrome.tabs.query({ active: true }, whitelistAll);
vAPI.tabs.get(null, whitelistAll);
break;
case 'open-dashboard':
µMatrix.utils.gotoExtensionURL('dashboard.html');
@ -59,7 +59,7 @@ var onCommand = function(command) {
/******************************************************************************/
chrome.commands.onCommand.addListener(onCommand);
// chrome.commands.onCommand.addListener(onCommand);
/******************************************************************************/

@ -19,113 +19,49 @@
Home: https://github.com/gorhill/uMatrix
*/
/* jshint multistr: true */
/* global chrome */
// Injected into content pages
/* global vAPI */
/* jshint multistr: true, boss: true */
/******************************************************************************/
/******************************************************************************/
// https://github.com/gorhill/httpswitchboard/issues/345
var messaging = (function(name){
var port = null;
var requestId = 1;
var requestIdToCallbackMap = {};
var listenCallback = null;
var onPortMessage = function(details) {
if ( typeof details.id !== 'number' ) {
return;
}
// Announcement?
if ( details.id < 0 ) {
if ( listenCallback ) {
listenCallback(details.msg);
}
return;
}
var callback = requestIdToCallbackMap[details.id];
if ( !callback ) {
return;
}
// Must be removed before calling client to be sure to not execute
// callback again if the client stops the messaging service.
delete requestIdToCallbackMap[details.id];
callback(details.msg);
};
// Injected into content pages
var start = function(name) {
port = chrome.runtime.connect({ name: name });
port.onMessage.addListener(onPortMessage);
(function() {
// https://github.com/gorhill/uBlock/issues/193
port.onDisconnect.addListener(stop);
};
'use strict';
var stop = function() {
listenCallback = null;
port.disconnect();
port = null;
flushCallbacks();
};
/******************************************************************************/
if ( typeof name === 'string' && name !== '' ) {
start(name);
}
// https://github.com/chrisaljoudi/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
//console.debug('contentscript-end.js > not a HTLMDocument');
return false;
}
var ask = function(msg, callback) {
if ( port === null ) {
if ( typeof callback === 'function' ) {
callback();
}
// This can happen
if ( !vAPI ) {
//console.debug('contentscript-end.js > vAPI not found');
return;
}
if ( callback === undefined ) {
tell(msg);
return;
}
var id = requestId++;
port.postMessage({ id: id, msg: msg });
requestIdToCallbackMap[id] = callback;
};
}
var tell = function(msg) {
if ( port !== null ) {
port.postMessage({ id: 0, msg: msg });
}
};
// https://github.com/chrisaljoudi/uBlock/issues/587
// Pointless to execute without the start script having done its job.
if ( !vAPI.contentscriptStartInjected ) {
return;
}
var listen = function(callback) {
listenCallback = callback;
};
// https://github.com/chrisaljoudi/uBlock/issues/456
// Already injected?
if ( vAPI.contentscriptEndInjected ) {
//console.debug('contentscript-end.js > content script already injected');
return;
}
vAPI.contentscriptEndInjected = true;
var flushCallbacks = function() {
var callback;
for ( var id in requestIdToCallbackMap ) {
if ( requestIdToCallbackMap.hasOwnProperty(id) === false ) {
continue;
}
callback = requestIdToCallbackMap[id];
if ( !callback ) {
continue;
}
// Must be removed before calling client to be sure to not execute
// callback again if the client stops the messaging service.
delete requestIdToCallbackMap[id];
callback();
}
};
/******************************************************************************/
return {
start: start,
stop: stop,
ask: ask,
tell: tell,
listen: listen
};
})('contentscript-end.js');
var localMessager = vAPI.messaging.channel('contentscript-end.js');
/******************************************************************************/
/******************************************************************************/
@ -154,12 +90,10 @@ var checkScriptBlacklistedHandler = function(response) {
}
};
messaging.ask({
localMessager.send({
what: 'checkScriptBlacklisted',
url: window.location.href
},
checkScriptBlacklistedHandler
);
}, checkScriptBlacklistedHandler);
/******************************************************************************/
@ -179,12 +113,10 @@ try {
var hasLocalStorage = window.localStorage && window.localStorage.length;
var hasSessionStorage = window.sessionStorage && window.sessionStorage.length;
if ( hasLocalStorage || hasSessionStorage ) {
messaging.ask({
localMessager.send({
what: 'contentScriptHasLocalStorage',
url: window.location.href
},
localStorageHandler
);
}, localStorageHandler);
}
// TODO: indexedDB
@ -291,28 +223,12 @@ var nodeListsAddedHandler = function(nodeLists) {
nodesAddedHandler(nodeLists[i], summary);
}
if ( summary.mustReport ) {
messaging.tell(summary);
localMessager.send(summary);
}
};
/******************************************************************************/
// rhill 2013-11-09: Weird... This code is executed from HTTP Switchboard
// context first time extension is launched. Avoid this.
// TODO: Investigate if this was a fluke or if it can really happen.
// I suspect this could only happen when I was using chrome.tabs.executeScript(),
// because now a delarative content script is used, along with "http{s}" URL
// pattern matching.
// console.debug('contentscript-end.js > window.location.href = "%s"', window.location.href);
if ( /^https?:\/\/./.test(window.location.href) === false ) {
console.debug("Huh?");
return;
}
/******************************************************************************/
(function() {
var summary = {
what: 'contentScriptSummary',
@ -329,7 +245,7 @@ if ( /^https?:\/\/./.test(window.location.href) === false ) {
//console.debug('contentscript-end.js > firstObservationHandler(): found %d script tags in "%s"', Object.keys(summary.scriptSources).length, window.location.href);
messaging.tell(summary);
localMessager.send(summary);
})();
/******************************************************************************/
@ -379,3 +295,8 @@ if ( document.body ) {
/******************************************************************************/
})();
/******************************************************************************/
/******************************************************************************/
})();

@ -19,124 +19,43 @@
Home: https://github.com/gorhill/uMatrix
*/
/* global vAPI */
/* jshint multistr: true */
/* global chrome */
// Injected into content pages
/******************************************************************************/
// OK, I keep changing my mind whether a closure should be used or not. This
// will be the rule: if there are any variables directly accessed on a regular
// basis, use a closure so that they are cached. Otherwise I don't think the
// overhead of a closure is worth it. That's my understanding.
(function() {
/******************************************************************************/
/******************************************************************************/
'use strict';
// https://github.com/gorhill/httpswitchboard/issues/345
/******************************************************************************/
var messaging = (function(name){
var port = null;
var requestId = 1;
var requestIdToCallbackMap = {};
var listenCallback = null;
// https://github.com/chrisaljoudi/uBlock/issues/464
if ( document instanceof HTMLDocument === false ) {
//console.debug('contentscript-start.js > not a HTLMDocument');
return false;
}
var onPortMessage = function(details) {
if ( typeof details.id !== 'number' ) {
return;
}
// Announcement?
if ( details.id < 0 ) {
if ( listenCallback ) {
listenCallback(details.msg);
}
return;
}
var callback = requestIdToCallbackMap[details.id];
if ( !callback ) {
// This can happen
if ( !vAPI ) {
//console.debug('contentscript-start.js > vAPI not found');
return;
}
// Must be removed before calling client to be sure to not execute
// callback again if the client stops the messaging service.
delete requestIdToCallbackMap[details.id];
callback(details.msg);
};
var start = function(name) {
port = chrome.runtime.connect({ name: name });
port.onMessage.addListener(onPortMessage);
// https://github.com/gorhill/uBlock/issues/193
port.onDisconnect.addListener(stop);
};
var stop = function() {
listenCallback = null;
port.disconnect();
port = null;
flushCallbacks();
};
if ( typeof name === 'string' && name !== '' ) {
start(name);
}
}
var ask = function(msg, callback) {
if ( port === null ) {
if ( typeof callback === 'function' ) {
callback();
}
// https://github.com/chrisaljoudi/uBlock/issues/456
// Already injected?
if ( vAPI.contentscriptStartInjected ) {
//console.debug('contentscript-end.js > content script already injected');
return;
}
if ( callback === undefined ) {
tell(msg);
return;
}
var id = requestId++;
port.postMessage({ id: id, msg: msg });
requestIdToCallbackMap[id] = callback;
};
var tell = function(msg) {
if ( port !== null ) {
port.postMessage({ id: 0, msg: msg });
}
};
var listen = function(callback) {
listenCallback = callback;
};
}
vAPI.contentscriptStartInjected = true;
var flushCallbacks = function() {
var callback;
for ( var id in requestIdToCallbackMap ) {
if ( requestIdToCallbackMap.hasOwnProperty(id) === false ) {
continue;
}
callback = requestIdToCallbackMap[id];
if ( !callback ) {
continue;
}
// Must be removed before calling client to be sure to not execute
// callback again if the client stops the messaging service.
delete requestIdToCallbackMap[id];
callback();
}
};
/******************************************************************************/
return {
start: start,
stop: stop,
ask: ask,
tell: tell,
listen: listen
};
})('contentscript-start.js');
var localMessager = vAPI.messaging.channel('contentscript-start.js');
/******************************************************************************/
/******************************************************************************/
// If you play with this code, mind:
@ -193,14 +112,13 @@ var injectNavigatorSpoofer = function(spoofedUserAgent) {
// The port will never be used again at this point, disconnecting allows
// to browser to flush this script from memory.
messaging.stop();
localMessager.close();
};
var requestDetails = {
localMessager.send({
what: 'getUserAgentReplaceStr',
hostname: window.location.hostname
};
messaging.ask(requestDetails, injectNavigatorSpoofer);
}, injectNavigatorSpoofer);
/******************************************************************************/
/******************************************************************************/

@ -301,7 +301,7 @@ var chromeCookieRemove = function(url, name) {
}
};
chrome.cookies.remove({ url: url, name: name }, callback);
vAPI.cookies.remove({ url: url, name: name }, callback);
};
/******************************************************************************/
@ -485,7 +485,7 @@ var canRemoveCookie = function(cookieKey, srcHostnames) {
// Listen to any change in cookieland, we will update page stats accordingly.
var onChromeCookieChanged = function(changeInfo) {
vAPI.cookies.onChanged = function(changeInfo) {
if ( changeInfo.removed ) {
return;
}
@ -524,8 +524,8 @@ var onChromeCookieChanged = function(changeInfo) {
/******************************************************************************/
chrome.cookies.getAll({}, addCookiesToDict);
chrome.cookies.onChanged.addListener(onChromeCookieChanged);
vAPI.cookies.getAll(addCookiesToDict);
vAPI.cookies.registerListeners();
µm.asyncJobs.add('cookieHunterRemove', null, processRemoveQueue, 2 * 60 * 1000, true);
µm.asyncJobs.add('cookieHunterClean', null, processClean, 10 * 60 * 1000, true);

@ -25,10 +25,14 @@
(function() {
'use strict';
/******************************************************************************/
var loadDashboardPanel = function(hash) {
var button = uDom(hash);
var url = button.attr('data-dashboard-panel-url');
uDom('iframe').nodeAt(0).src = url;
uDom('iframe').attr('src', url);
uDom('.tabButton').forEach(function(button){
button.toggleClass('selected', button.attr('data-dashboard-panel-url') === url);
});

@ -19,27 +19,16 @@
Home: https://github.com/gorhill/uMatrix
*/
/* global chrome, messaging, uDom */
/* global vAPI, uDom */
/******************************************************************************/
(function() {
/******************************************************************************/
var listDetails = {};
var externalHostsFiles = '';
var cacheWasPurged = false;
var needUpdate = false;
var hasCachedContent = false;
var re3rdPartyExternalAsset = /^https?:\/\/[a-z0-9]+/;
var re3rdPartyRepoAsset = /^assets\/thirdparties\/([^\/]+)/;
'use strict';
/******************************************************************************/
messaging.start('hosts-files.js');
var onMessage = function(msg) {
switch ( msg.what ) {
case 'loadHostsFilesCompleted':
@ -51,7 +40,18 @@ var onMessage = function(msg) {
}
};
messaging.listen(onMessage);
var messager = vAPI.messaging.channel('hosts-files.js', onMessage);
/******************************************************************************/
var listDetails = {};
var externalHostsFiles = '';
var cacheWasPurged = false;
var needUpdate = false;
var hasCachedContent = false;
var re3rdPartyExternalAsset = /^https?:\/\/[a-z0-9]+/;
var re3rdPartyRepoAsset = /^assets\/thirdparties\/([^\/]+)/;
/******************************************************************************/
@ -94,9 +94,9 @@ var renderBlacklists = function() {
return html.join('');
};
var purgeButtontext = chrome.i18n.getMessage('hostsFilesExternalListPurge');
var updateButtontext = chrome.i18n.getMessage('hostsFilesExternalListNew');
var obsoleteButtontext = chrome.i18n.getMessage('hostsFilesExternalListObsolete');
var purgeButtontext = vAPI.i18n('hostsFilesExternalListPurge');
var updateButtontext = vAPI.i18n('hostsFilesExternalListNew');
var obsoleteButtontext = vAPI.i18n('hostsFilesExternalListObsolete');
var liTemplate = [
'<li class="listDetails">',
'<input type="checkbox" {{checked}}>',
@ -107,7 +107,7 @@ var renderBlacklists = function() {
'{{homeURL}}',
': ',
'<span class="dim">',
chrome.i18n.getMessage('hostsFilesPerFileStats'),
vAPI.i18n('hostsFilesPerFileStats'),
'</span>'
].join('');
@ -181,7 +181,7 @@ var renderBlacklists = function() {
var html = htmlBuiltin.concat(htmlExternal);
uDom('#listsOfBlockedHostsPrompt').text(
chrome.i18n.getMessage('hostsFilesStats')
vAPI.i18n('hostsFilesStats')
.replace('{{blockedHostnameCount}}', details.blockedHostnameCount.toLocaleString())
);
uDom('#autoUpdate').prop('checked', listDetails.autoUpdate === true);
@ -191,7 +191,7 @@ var renderBlacklists = function() {
updateWidgets();
};
messaging.ask({ what: 'getLists' }, onListsReceived);
messager.send({ what: 'getLists' }, onListsReceived);
};
/******************************************************************************/
@ -266,7 +266,7 @@ var onListCheckboxChanged = function() {
/******************************************************************************/
var onListLinkClicked = function(ev) {
messaging.tell({
messager.send({
what: 'gotoExtensionURL',
url: 'asset-viewer.html?url=' + uDom(this).attr('href')
});
@ -282,7 +282,7 @@ var onPurgeClicked = function() {
if ( !href ) {
return;
}
messaging.tell({ what: 'purgeCache', path: href });
messager.send({ what: 'purgeCache', path: href });
button.remove();
if ( li.descendants('input').first().prop('checked') ) {
cacheWasPurged = true;
@ -312,7 +312,7 @@ var reloadAll = function(update) {
off: lis.subset(i, 1).descendants('input').prop('checked') === false
});
}
messaging.tell({
messager.send({
what: 'reloadHostsFiles',
switches: switches,
update: update
@ -341,13 +341,13 @@ var buttonPurgeAllHandler = function() {
var onCompleted = function() {
renderBlacklists();
};
messaging.ask({ what: 'purgeAllCaches' }, onCompleted);
messager.send({ what: 'purgeAllCaches' }, onCompleted);
};
/******************************************************************************/
var autoUpdateCheckboxChanged = function() {
messaging.tell({
messager.send({
what: 'userSettings',
name: 'autoUpdate',
value: this.checked
@ -361,7 +361,7 @@ var renderExternalLists = function() {
uDom('#externalHostsFiles').val(details);
externalHostsFiles = details;
};
messaging.ask({ what: 'userSettings', name: 'externalHostsFiles' }, onReceived);
messager.send({ what: 'userSettings', name: 'externalHostsFiles' }, onReceived);
};
/******************************************************************************/
@ -377,7 +377,7 @@ var externalListsChangeHandler = function() {
var externalListsApplyHandler = function() {
externalHostsFiles = uDom('#externalHostsFiles').val();
messaging.tell({
messager.send({
what: 'userSettings',
name: 'externalHostsFiles',
value: externalHostsFiles

@ -198,27 +198,11 @@
/******************************************************************************/
µMatrix.turnOff = function() {
// rhill 2013-12-07:
// Relinquish control over javascript execution to the user.
// https://github.com/gorhill/httpswitchboard/issues/74
chrome.contentSettings.javascript.clear({});
vAPI.app.start();
};
µMatrix.turnOn = function() {
chrome.contentSettings.javascript.clear({});
// rhill 2013-12-07:
// Tell Chromium to allow all javascript: µMatrix will control whether
// javascript execute through `Content-Policy-Directive` and webRequest.
// https://github.com/gorhill/httpswitchboard/issues/74
chrome.contentSettings.javascript.set({
primaryPattern: 'https://*/*',
setting: 'allow'
});
chrome.contentSettings.javascript.set({
primaryPattern: 'http://*/*',
setting: 'allow'
});
vAPI.app.stop();
};
/******************************************************************************/

@ -37,7 +37,7 @@ window.addEventListener('load', function() {
var node;
while ( i-- ) {
node = nodeList[i];
node.innerHTML = chrome.i18n.getMessage(node.getAttribute('data-i18n'));
node.innerHTML = vAPI.i18n(node.getAttribute('data-i18n'));
}
// copy text of <h1> if any to document title
node = document.querySelector('h1');
@ -49,6 +49,6 @@ window.addEventListener('load', function() {
i = nodeList.length;
while ( i-- ) {
node = nodeList[i];
node.setAttribute('data-tip', chrome.i18n.getMessage(node.getAttribute('data-i18n-tip')));
node.setAttribute('data-tip', vAPI.i18n(node.getAttribute('data-i18n-tip')));
}
});

@ -19,15 +19,17 @@
Home: https://github.com/gorhill/uMatrix
*/
/* global messaging, uDom */
/* global vAPI, uDom */
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
messaging.start('info.js');
var messager = vAPI.messaging.channel('info.js');
var targetUrl = 'all';
var maxRequests = 500;
@ -55,7 +57,7 @@ function updateRequestData(callback) {
what: 'getRequestLogs',
pageURL: targetUrl !== 'all' ? targetUrl : null
};
messaging.ask(request, onResponseReceived);
messager.send(request, onResponseReceived);
}
/******************************************************************************/
@ -65,7 +67,7 @@ function clearRequestData() {
what: 'clearRequestLogs',
pageURL: targetUrl !== 'all' ? targetUrl : null
};
messaging.tell(request);
messager.send(request);
}
/******************************************************************************/
@ -93,7 +95,7 @@ function renderNumbers(set) {
var renderLocalized = function(id, map) {
var uElem = uDom('#' + id);
var msg = chrome.i18n.getMessage(id);
var msg = vAPI.i18n(id);
for ( var k in map ) {
if ( map.hasOwnProperty(k) === false ) {
continue;
@ -144,7 +146,7 @@ function renderPageUrls() {
// Select whatever needs to be selected
//uDom('#selectPageUrls > option[value="'+targetUrl+'"]').prop('selected', true);
};
messaging.ask({ what: 'getPageURLs' }, onResponseReceived);
messager.send({ what: 'getPageURLs' }, onResponseReceived);
}
/******************************************************************************/
@ -193,7 +195,7 @@ function renderStats() {
uDom('a').attr('target', '_blank');
};
messaging.ask({
messager.send({
what: 'getStats',
pageURL: targetUrl === 'all' ? null : targetUrl
},
@ -285,7 +287,7 @@ var clearRequests = function() {
function changeUserSettings(name, value) {
cachedUserSettings[name] = value;
messaging.tell({
messager.send({
what: 'userSettings',
name: name,
value: value
@ -404,7 +406,7 @@ uDom.onLoad(function(){
installEventHandlers();
};
messaging.ask({ what: 'getUserSettings' }, onResponseReceived);
messager.send({ what: 'getUserSettings' }, onResponseReceived);
renderTransientData(true);
updateRequests();

@ -1,141 +0,0 @@
/*******************************************************************************
µMatrix - a Chromium browser extension to black/white list requests.
Copyright (C) 2014 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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uMatrix
*/
// This is the reference client-side implementation of µBlock's messaging
// infrastructure. The "server"-side implementation is in messaging.js.
// The client-side implementation creates a port in order to connect to
// µBlock's background page. With this port we can "ask", "tell" or "announce":
//
// "ask": send a request and expect an answer using a callback.
// "tell": send a request with no expectation of an answer.
// "announce": send a request to be relayed to all connections -- no answer
// expected.
//
// The tricky part in this implementation is to ensure all the requests are
// uniquely identified, so that the background-page can keep track of these
// until it is ready to send back an answer, which will be tagged with the
// same id. The uniqueness must be true for all ports which connect to the
// background page at any given time.
//
// Currently using Math.random() to generate this id... I don't know about the
// implementation of Math.random(), but as long as I have a good expectation
// of uniqueness, it's ok, we are not dealing with critical stuff here.
/* global chrome */
var messaging = (function(name){
var port = null;
var requestId = 1;
var requestIdToCallbackMap = {};
var listenCallback = null;
var onPortMessage = function(details) {
if ( typeof details.id !== 'number' ) {
return;
}
// Announcement?
if ( details.id < 0 ) {
if ( listenCallback ) {
listenCallback(details.msg);
}
return;
}
var callback = requestIdToCallbackMap[details.id];
if ( !callback ) {
return;
}
// Must be removed before calling client to be sure to not execute
// callback again if the client stops the messaging service.
delete requestIdToCallbackMap[details.id];
callback(details.msg);
};
var start = function(name) {
port = chrome.runtime.connect({ name: name });
port.onMessage.addListener(onPortMessage);
// https://github.com/gorhill/uBlock/issues/193
port.onDisconnect.addListener(stop);
};
var stop = function() {
listenCallback = null;
port.disconnect();
port = null;
flushCallbacks();
};
if ( typeof name === 'string' && name !== '' ) {
start(name);
}
var ask = function(msg, callback) {
if ( port === null ) {
if ( typeof callback === 'function' ) {
callback();
}
return;
}
if ( callback === undefined ) {
tell(msg);
return;
}
var id = requestId++;
port.postMessage({ id: id, msg: msg });
requestIdToCallbackMap[id] = callback;
};
var tell = function(msg) {
if ( port !== null ) {
port.postMessage({ id: 0, msg: msg });
}
};
var listen = function(callback) {
listenCallback = callback;
};
var flushCallbacks = function() {
var callback;
for ( var id in requestIdToCallbackMap ) {
if ( requestIdToCallbackMap.hasOwnProperty(id) === false ) {
continue;
}
callback = requestIdToCallbackMap[id];
if ( !callback ) {
continue;
}
// Must be removed before calling client to be sure to not execute
// callback again if the client stops the messaging service.
delete requestIdToCallbackMap[id];
callback();
}
};
return {
start: start,
stop: stop,
ask: ask,
tell: tell,
listen: listen
};
})();

@ -1,816 +0,0 @@
/*******************************************************************************
µMatrix - a Chromium browser extension to black/white list requests.
Copyright (C) 2014 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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uMatrix
*/
/* global chrome, µMatrix */
/* jshint boss: true */
/******************************************************************************/
/******************************************************************************/
(function() {
// popup.js
var µm = µMatrix;
/******************************************************************************/
var smartReload = function(tabs) {
var i = tabs.length;
while ( i-- ) {
µm.smartReloadTabs(µm.userSettings.smartAutoReload, tabs[i].id);
}
};
/******************************************************************************/
// Constructor is faster than object literal
var RowSnapshot = function(srcHostname, desHostname, desDomain) {
this.domain = desDomain;
this.temporary = µm.tMatrix.evaluateRowZXY(srcHostname, desHostname);
this.permanent = µm.pMatrix.evaluateRowZXY(srcHostname, desHostname);
this.counts = RowSnapshot.counts.slice();
this.totals = RowSnapshot.counts.slice();
};
RowSnapshot.counts = (function() {
var i = Object.keys(µm.Matrix.getColumnHeaders()).length;
var aa = new Array(i);
while ( i-- ) {
aa[i] = 0;
}
return aa;
})();
/******************************************************************************/
var matrixSnapshot = function(details) {
var µmuser = µm.userSettings;
var r = {
tabId: details.tabId,
url: '',
hostname: '',
domain: '',
blockedCount: 0,
scope: '*',
headers: µm.Matrix.getColumnHeaders(),
tSwitches: {},
pSwitches: {},
rows: {},
rowCount: 0,
diff: [],
userSettings: {
colorBlindFriendly: µmuser.colorBlindFriendly,
displayTextSize: µmuser.displayTextSize,
popupCollapseDomains: µmuser.popupCollapseDomains,
popupCollapseSpecificDomains: µmuser.popupCollapseSpecificDomains,
popupHideBlacklisted: µmuser.popupHideBlacklisted,
popupScopeLevel: µmuser.popupScopeLevel
}
};
// Allow examination of behind-the-scene requests
// TODO: Not portable
if ( details.tabURL ) {
if ( details.tabURL.indexOf('chrome-extension://' + chrome.runtime.id + '/') === 0 ) {
details.tabId = µm.behindTheSceneTabId;
} else if ( details.tabURL === µm.behindTheSceneURL ) {
details.tabId = µm.behindTheSceneTabId;
}
}
var pageStore = µm.pageStatsFromTabId(details.tabId);
if ( !pageStore ) {
return r;
}
var headers = r.headers;
r.url = pageStore.pageUrl;
r.hostname = pageStore.pageHostname;
r.domain = pageStore.pageDomain;
r.blockedCount = pageStore.requestStats.blocked.all;
if ( µmuser.popupScopeLevel === 'site' ) {
r.scope = r.hostname;
} else if ( µmuser.popupScopeLevel === 'domain' ) {
r.scope = r.domain;
}
var switchNames = µm.Matrix.getSwitchNames();
for ( var switchName in switchNames ) {
if ( switchNames.hasOwnProperty(switchName) === false ) {
continue;
}
r.tSwitches[switchName] = µm.tMatrix.evaluateSwitchZ(switchName, r.scope);
r.pSwitches[switchName] = µm.pMatrix.evaluateSwitchZ(switchName, r.scope);
}
// These rows always exist
r.rows['*'] = new RowSnapshot(r.scope, '*', '*');
r.rows['1st-party'] = new RowSnapshot(r.scope, '1st-party', '1st-party');
r.rowCount += 1;
var µmuri = µm.URI;
var reqKey, reqType, reqHostname, reqDomain;
var desHostname;
var row, typeIndex;
var anyIndex = headers['*'];
var pageRequests = pageStore.requests;
var reqKeys = pageRequests.getRequestKeys();
var iReqKey = reqKeys.length;
var pos;
while ( iReqKey-- ) {
reqKey = reqKeys[iReqKey];
reqType = pageRequests.typeFromRequestKey(reqKey);
reqHostname = pageRequests.hostnameFromRequestKey(reqKey);
// rhill 2013-10-23: hostname can be empty if the request is a data url
// https://github.com/gorhill/httpswitchboard/issues/26
if ( reqHostname === '' ) {
reqHostname = pageStore.pageHostname;
}
reqDomain = µmuri.domainFromHostname(reqHostname) || reqHostname;
// We want rows of self and ancestors
desHostname = reqHostname;
for ( ;; ) {
// If row exists, ancestors exist
if ( r.rows.hasOwnProperty(desHostname) !== false ) {
break;
}
r.rows[desHostname] = new RowSnapshot(r.scope, desHostname, reqDomain);
r.rowCount += 1;
if ( desHostname === reqDomain ) {
break;
}
pos = desHostname.indexOf('.');
if ( pos === -1 ) {
break;
}
desHostname = desHostname.slice(pos + 1);
}
typeIndex = headers[reqType];
row = r.rows[reqHostname];
row.counts[typeIndex] += 1;
row.counts[anyIndex] += 1;
row = r.rows[reqDomain];
row.totals[typeIndex] += 1;
row.totals[anyIndex] += 1;
row = r.rows['*'];
row.totals[typeIndex] += 1;
row.totals[anyIndex] += 1;
}
r.diff = µm.tMatrix.diff(µm.pMatrix, r.hostname, Object.keys(r.rows));
return r;
};
/******************************************************************************/
var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'disconnected':
// https://github.com/gorhill/httpswitchboard/issues/94
if ( µm.userSettings.smartAutoReload ) {
chrome.tabs.query({ active: true }, smartReload);
}
break;
case 'matrixSnapshot':
response = matrixSnapshot(request);
break;
case 'toggleMatrixSwitch':
µm.tMatrix.setSwitchZ(
request.switchName,
request.srcHostname,
µm.tMatrix.evaluateSwitchZ(request.switchName, request.srcHostname) === false
);
break;
case 'blacklistMatrixCell':
µm.tMatrix.blacklistCell(
request.srcHostname,
request.desHostname,
request.type
);
break;
case 'whitelistMatrixCell':
µm.tMatrix.whitelistCell(
request.srcHostname,
request.desHostname,
request.type
);
break;
case 'graylistMatrixCell':
µm.tMatrix.graylistCell(
request.srcHostname,
request.desHostname,
request.type
);
break;
case 'applyDiffToPermanentMatrix': // aka "persist"
if ( µm.pMatrix.applyDiff(request.diff, µm.tMatrix) ) {
µm.saveMatrix();
}
break;
case 'applyDiffToTemporaryMatrix': // aka "revert"
µm.tMatrix.applyDiff(request.diff, µm.pMatrix);
break;
case 'revertTemporaryMatrix':
µm.tMatrix.assign(µm.pMatrix);
break;
default:
return µm.messaging.defaultHandler(request, sender, callback);
}
callback(response);
};
µm.messaging.listen('popup.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
// content scripts
(function() {
var µm = µMatrix;
/******************************************************************************/
var contentScriptSummaryHandler = function(tabId, details) {
// TODO: Investigate "Error in response to tabs.executeScript: TypeError:
// Cannot read property 'locationURL' of null" (2013-11-12). When can this
// happens?
if ( !details || !details.locationURL ) {
return;
}
var pageURL = µm.pageUrlFromTabId(tabId);
var pageStats = µm.pageStatsFromPageUrl(pageURL);
var µmuri = µm.URI.set(details.locationURL);
var frameURL = µmuri.normalizedURI();
var frameHostname = µmuri.hostname;
var urls, url, r;
// https://github.com/gorhill/httpswitchboard/issues/333
// Look-up here whether inline scripting is blocked for the frame.
var inlineScriptBlocked = µm.mustBlock(µm.scopeFromURL(pageURL), frameHostname, 'script');
// scripts
// https://github.com/gorhill/httpswitchboard/issues/25
if ( pageStats && inlineScriptBlocked ) {
urls = details.scriptSources;
for ( url in urls ) {
if ( !urls.hasOwnProperty(url) ) {
continue;
}
if ( url === '{inline_script}' ) {
url = frameURL + '{inline_script}';
}
r = µm.filterRequest(pageURL, 'script', url);
pageStats.recordRequest('script', url, r !== false, r);
}
}
// TODO: as of 2014-05-26, not sure this is needed anymore, since µMatrix
// no longer uses chrome.contentSettings API (I think that was the reason
// this code was put in).
// plugins
// https://github.com/gorhill/httpswitchboard/issues/25
if ( pageStats ) {
urls = details.pluginSources;
for ( url in urls ) {
if ( !urls.hasOwnProperty(url) ) {
continue;
}
r = µm.filterRequest(pageURL, 'plugin', url);
pageStats.recordRequest('plugin', url, r !== false, r);
}
}
// https://github.com/gorhill/httpswitchboard/issues/181
µm.onPageLoadCompleted(pageURL);
};
/******************************************************************************/
var contentScriptLocalStorageHandler = function(pageURL) {
var µmuri = µm.URI.set(pageURL);
var response = µm.mustBlock(µm.scopeFromURL(pageURL), µmuri.hostname, 'cookie');
µm.recordFromPageUrl(
pageURL,
'cookie',
µmuri.rootURL() + '/{localStorage}',
response
);
response = response && µm.userSettings.deleteLocalStorage;
if ( response ) {
µm.localStorageRemovedCounter++;
}
return response;
};
/******************************************************************************/
var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
default:
break;
}
var tabId = sender.tab.id;
// Sync
var response;
switch ( request.what ) {
case 'contentScriptHasLocalStorage':
response = contentScriptLocalStorageHandler(request.url);
µm.updateBadgeAsync(tabId);
break;
case 'contentScriptSummary':
contentScriptSummaryHandler(tabId, request);
µm.updateBadgeAsync(tabId);
break;
case 'checkScriptBlacklisted':
response = {
scriptBlacklisted: µm.mustBlock(
µm.scopeFromURL(request.url),
µm.hostnameFromURL(request.url),
'script'
)
};
break;
case 'getUserAgentReplaceStr':
response = µm.tMatrix.evaluateSwitchZ('ua-spoof', request.hostname) ?
µm.userAgentReplaceStr :
undefined;
break;
default:
return µm.messaging.defaultHandler(request, sender, callback);
}
callback(response);
};
µMatrix.messaging.listen('contentscript-start.js', onMessage);
µMatrix.messaging.listen('contentscript-end.js', onMessage);
/******************************************************************************/
})();
/******************************************************************************/
/******************************************************************************/
// settings.js
(function() {
var onMessage = function(request, sender, callback) {
var µm = µMatrix;
// Async
switch ( request.what ) {
default:
break;
}
// Sync
var response;
switch ( request.what ) {
default:
return µm.messaging.defaultHandler(request, sender, callback);
}
callback(response);
};
µMatrix.messaging.listen('settings.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
// privacy.js
(function() {
var onMessage = function(request, sender, callback) {
var µm = µMatrix;
// Async
switch ( request.what ) {
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'getPrivacySettings':
response = {
userSettings: µm.userSettings,
matrixSwitches: {
'https-strict': µm.pMatrix.evaluateSwitch('https-strict', '*') === 1,
'ua-spoof': µm.pMatrix.evaluateSwitch('ua-spoof', '*') === 1,
'referrer-spoof': µm.pMatrix.evaluateSwitch('referrer-spoof', '*') === 1
}
};
break;
case 'setMatrixSwitch':
µm.tMatrix.setSwitch(request.switchName, '*', request.state);
if ( µm.pMatrix.setSwitch(request.switchName, '*', request.state) ) {
µm.saveMatrix();
}
break;
default:
return µm.messaging.defaultHandler(request, sender, callback);
}
callback(response);
};
µMatrix.messaging.listen('privacy.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
// user-rules.js
(function() {
var µm = µMatrix;
/******************************************************************************/
var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'getUserRules':
response = {
temporaryRules: µm.tMatrix.toString(),
permanentRules: µm.pMatrix.toString()
};
break;
case 'setUserRules':
if ( typeof request.temporaryRules === 'string' ) {
µm.tMatrix.fromString(request.temporaryRules);
}
if ( typeof request.permanentRules === 'string' ) {
µm.pMatrix.fromString(request.permanentRules);
µm.saveMatrix();
}
response = {
temporaryRules: µm.tMatrix.toString(),
permanentRules: µm.pMatrix.toString()
};
break;
default:
return µm.messaging.defaultHandler(request, sender, callback);
}
callback(response);
};
µMatrix.messaging.listen('user-rules.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
// hosts-files.js
(function() {
var µm = µMatrix;
/******************************************************************************/
var getLists = function(callback) {
var r = {
available: null,
cache: null,
current: µm.liveHostsFiles,
blockedHostnameCount: µm.ubiquitousBlacklist.count,
autoUpdate: µm.userSettings.autoUpdate
};
var onMetadataReady = function(entries) {
r.cache = entries;
callback(r);
};
var onAvailableHostsFilesReady = function(lists) {
r.available = lists;
µm.assets.metadata(onMetadataReady);
};
µm.getAvailableHostsFiles(onAvailableHostsFilesReady);
};
/******************************************************************************/
var onMessage = function(request, sender, callback) {
var µm = µMatrix;
// Async
switch ( request.what ) {
case 'getLists':
return getLists(callback);
case 'purgeAllCaches':
return µm.assets.purgeAll(callback);
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'purgeCache':
µm.assets.purge(request.path);
break;
default:
return µm.messaging.defaultHandler(request, sender, callback);
}
callback(response);
};
µMatrix.messaging.listen('hosts-files.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
// info.js
(function() {
/******************************************************************************/
// map(pageURL) => array of request log entries
var getRequestLog = function(pageURL) {
var requestLogs = {};
var pageStores = µMatrix.pageStats;
var pageURLs = pageURL ? [pageURL] : Object.keys(pageStores);
var pageStore, pageRequestLog, logEntries, j, logEntry;
for ( var i = 0; i < pageURLs.length; i++ ) {
pageURL = pageURLs[i];
pageStore = pageStores[pageURL];
if ( !pageStore ) {
continue;
}
pageRequestLog = [];
logEntries = pageStore.requests.getLoggedRequests();
j = logEntries.length;
while ( j-- ) {
// rhill 2013-12-04: `logEntry` can be null since a ring buffer is
// now used, and it might not have been filled yet.
if ( logEntry = logEntries[j] ) {
pageRequestLog.push(logEntry);
}
}
requestLogs[pageURL] = pageRequestLog;
}
return requestLogs;
};
/******************************************************************************/
var clearRequestLog = function(pageURL) {
var pageStores = µMatrix.pageStats;
var pageURLs = pageURL ? [pageURL] : Object.keys(pageStores);
var pageStore;
for ( var i = 0; i < pageURLs.length; i++ ) {
if ( pageStore = pageStores[pageURLs[i]] ) {
pageStore.requests.clearLogBuffer();
}
}
};
/******************************************************************************/
var onMessage = function(request, sender, callback) {
var µm = µMatrix;
// Async
switch ( request.what ) {
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'getPageURLs':
response = {
pageURLs: Object.keys(µm.pageUrlToTabId),
behindTheSceneURL: µm.behindTheSceneURL
};
break;
case 'getStats':
var pageStore = µm.pageStats[request.pageURL];
response = {
globalNetStats: µm.requestStats,
pageNetStats: pageStore ? pageStore.requestStats : null,
cookieHeaderFoiledCounter: µm.cookieHeaderFoiledCounter,
refererHeaderFoiledCounter: µm.refererHeaderFoiledCounter,
hyperlinkAuditingFoiledCounter: µm.hyperlinkAuditingFoiledCounter,
cookieRemovedCounter: µm.cookieRemovedCounter,
localStorageRemovedCounter: µm.localStorageRemovedCounter,
browserCacheClearedCounter: µm.browserCacheClearedCounter
};
break;
case 'getRequestLogs':
response = getRequestLog(request.pageURL);
break;
case 'clearRequestLogs':
clearRequestLog(request.pageURL);
break;
default:
return µm.messaging.defaultHandler(request, sender, callback);
}
callback(response);
};
µMatrix.messaging.listen('info.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
// about.js
(function() {
var µm = µMatrix;
/******************************************************************************/
var restoreUserData = function(userData) {
var countdown = 3;
var onCountdown = function() {
countdown -= 1;
if ( countdown === 0 ) {
µm.XAL.restart();
}
};
var onAllRemoved = function() {
// Be sure to adjust `countdown` if adding/removing anything below
µm.XAL.keyvalSetMany(userData.settings, onCountdown);
µm.XAL.keyvalSetOne('userMatrix', userData.rules, onCountdown);
µm.XAL.keyvalSetOne('liveHostsFiles', userData.hostsFiles, onCountdown);
};
// If we are going to restore all, might as well wipe out clean local
// storage
µm.XAL.keyvalRemoveAll(onAllRemoved);
};
/******************************************************************************/
var resetUserData = function() {
var onAllRemoved = function() {
µm.XAL.restart();
};
µm.XAL.keyvalRemoveAll(onAllRemoved);
};
/******************************************************************************/
var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'getAllUserData':
response = {
app: 'µMatrix',
version: µm.manifest.version,
when: Date.now(),
settings: µm.userSettings,
rules: µm.pMatrix.toString(),
hostsFiles: µm.liveHostsFiles
};
break;
case 'getSomeStats':
response = {
version: µm.manifest.version,
storageQuota: µm.storageQuota,
storageUsed: µm.storageUsed
};
break;
case 'restoreAllUserData':
restoreUserData(request.userData);
break;
case 'resetAllUserData':
resetUserData();
break;
default:
return µm.messaging.defaultHandler(request, sender, callback);
}
callback(response);
};
µMatrix.messaging.listen('about.js', onMessage);
/******************************************************************************/
})();
/******************************************************************************/

@ -19,128 +19,490 @@
Home: https://github.com/gorhill/uMatrix
*/
// So there might be memory leaks related to the direct use of sendMessage(),
// as per https://code.google.com/p/chromium/issues/detail?id=320723. The issue
// is not marked as resolved, and the last message from chromium dev is:
//
// "You can construct Port objects (runtime.connect) and emulate sendMessage
// "behaviour. The bug is that sendMessage doesn't clean up its Ports."
//
// So the point here is to have an infrastructure which allows relying more on
// direct use of Port objects rather than going through sendMessage().
/* global µMatrix, vAPI */
/* jshint boss: true */
/******************************************************************************/
/*******************************************************************************
/******************************************************************************/
// Default handler
(function() {
'use strict';
var µm = µMatrix;
/******************************************************************************/
// Default is for commonly used message.
function onMessage(request, sender, callback) {
// Async
switch ( request.what ) {
case 'getAssetContent':
return µm.assets.getLocal(request.url, callback);
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'forceReloadTab':
µm.forceReload(request.tabId);
break;
case 'getUserSettings':
response = µm.userSettings;
break;
case 'gotoExtensionURL':
µm.utils.gotoExtensionURL(request.url);
break;
case 'gotoURL':
µm.utils.gotoURL(request);
break;
case 'reloadHostsFiles':
µm.reloadHostsFiles(request.switches, request.update);
break;
case 'userSettings':
response = µm.changeUserSettings(request.name, request.value);
break;
default:
return vAPI.messaging.UNHANDLED;
}
// Here this is the "server"-side implementation.
//
// Reference client-side implementation is found in:
//
// messaging-client.js
//
// For instance, it needs to be cut & pasted for content scripts since
// I can not include in a simple way js file content from another js file.
callback(response);
}
*******************************************************************************/
/******************************************************************************/
µMatrix.messaging = (function() {
vAPI.messaging.setup(onMessage);
/******************************************************************************/
})();
/******************************************************************************/
/******************************************************************************/
(function() {
// popup.js
var µm = µMatrix;
var runtimeIdGenerator = 1;
var nameToPortMap = {};
var nameToListenerMap = {};
var nullFunc = function(){};
/******************************************************************************/
var listenerNameFromPortName = function(portName) {
var pos = portName.indexOf('/');
if ( pos === -1 ) {
return '';
var smartReload = function(tabs) {
var i = tabs.length;
while ( i-- ) {
µm.smartReloadTabs(µm.userSettings.smartAutoReload, tabs[i].id);
}
return portName.slice(0, pos);
};
var listenerFromPortName = function(portName) {
return nameToListenerMap[listenerNameFromPortName(portName)];
/******************************************************************************/
// Constructor is faster than object literal
var RowSnapshot = function(srcHostname, desHostname, desDomain) {
this.domain = desDomain;
this.temporary = µm.tMatrix.evaluateRowZXY(srcHostname, desHostname);
this.permanent = µm.pMatrix.evaluateRowZXY(srcHostname, desHostname);
this.counts = RowSnapshot.counts.slice();
this.totals = RowSnapshot.counts.slice();
};
RowSnapshot.counts = (function() {
var i = Object.keys(µm.Matrix.getColumnHeaders()).length;
var aa = new Array(i);
while ( i-- ) {
aa[i] = 0;
}
return aa;
})();
/******************************************************************************/
var listen = function(portName, callback) {
var listener = nameToListenerMap[portName];
if ( listener && listener !== callback ) {
throw 'Only one listener allowed';
var matrixSnapshot = function(tabId, details) {
var µmuser = µm.userSettings;
var r = {
tabId: tabId,
url: '',
hostname: '',
domain: '',
blockedCount: 0,
scope: '*',
headers: µm.Matrix.getColumnHeaders(),
tSwitches: {},
pSwitches: {},
rows: {},
rowCount: 0,
diff: [],
userSettings: {
colorBlindFriendly: µmuser.colorBlindFriendly,
displayTextSize: µmuser.displayTextSize,
popupCollapseDomains: µmuser.popupCollapseDomains,
popupCollapseSpecificDomains: µmuser.popupCollapseSpecificDomains,
popupHideBlacklisted: µmuser.popupHideBlacklisted,
popupScopeLevel: µmuser.popupScopeLevel
}
nameToListenerMap[portName] = callback;
};
};
/******************************************************************************/
// Allow examination of behind-the-scene requests
// TODO: Not portable
if ( details.tabURL ) {
if ( details.tabURL.lastIndexOf(vAPI.getURL(''), 0) === 0 ) {
tabId = µm.behindTheSceneTabId;
} else if ( details.tabURL === µm.behindTheSceneURL ) {
tabId = µm.behindTheSceneTabId;
}
}
var pageStore = µm.pageStatsFromTabId(tabId);
if ( !pageStore ) {
return r;
}
var headers = r.headers;
var tell = function(target, msg) {
target += '/';
for ( var portName in nameToPortMap ) {
if ( nameToPortMap.hasOwnProperty(portName) === false ) {
r.url = pageStore.pageUrl;
r.hostname = pageStore.pageHostname;
r.domain = pageStore.pageDomain;
r.blockedCount = pageStore.requestStats.blocked.all;
if ( µmuser.popupScopeLevel === 'site' ) {
r.scope = r.hostname;
} else if ( µmuser.popupScopeLevel === 'domain' ) {
r.scope = r.domain;
}
var switchNames = µm.Matrix.getSwitchNames();
for ( var switchName in switchNames ) {
if ( switchNames.hasOwnProperty(switchName) === false ) {
continue;
}
if ( portName.indexOf(target) === 0 ) {
nameToPortMap[portName].postMessage({ id: -1, msg: msg });
r.tSwitches[switchName] = µm.tMatrix.evaluateSwitchZ(switchName, r.scope);
r.pSwitches[switchName] = µm.pMatrix.evaluateSwitchZ(switchName, r.scope);
}
// These rows always exist
r.rows['*'] = new RowSnapshot(r.scope, '*', '*');
r.rows['1st-party'] = new RowSnapshot(r.scope, '1st-party', '1st-party');
r.rowCount += 1;
var µmuri = µm.URI;
var reqKey, reqType, reqHostname, reqDomain;
var desHostname;
var row, typeIndex;
var anyIndex = headers['*'];
var pageRequests = pageStore.requests;
var reqKeys = pageRequests.getRequestKeys();
var iReqKey = reqKeys.length;
var pos;
while ( iReqKey-- ) {
reqKey = reqKeys[iReqKey];
reqType = pageRequests.typeFromRequestKey(reqKey);
reqHostname = pageRequests.hostnameFromRequestKey(reqKey);
// rhill 2013-10-23: hostname can be empty if the request is a data url
// https://github.com/gorhill/httpswitchboard/issues/26
if ( reqHostname === '' ) {
reqHostname = pageStore.pageHostname;
}
reqDomain = µmuri.domainFromHostname(reqHostname) || reqHostname;
// We want rows of self and ancestors
desHostname = reqHostname;
for ( ;; ) {
// If row exists, ancestors exist
if ( r.rows.hasOwnProperty(desHostname) !== false ) {
break;
}
r.rows[desHostname] = new RowSnapshot(r.scope, desHostname, reqDomain);
r.rowCount += 1;
if ( desHostname === reqDomain ) {
break;
}
pos = desHostname.indexOf('.');
if ( pos === -1 ) {
break;
}
desHostname = desHostname.slice(pos + 1);
}
typeIndex = headers[reqType];
row = r.rows[reqHostname];
row.counts[typeIndex] += 1;
row.counts[anyIndex] += 1;
row = r.rows[reqDomain];
row.totals[typeIndex] += 1;
row.totals[anyIndex] += 1;
row = r.rows['*'];
row.totals[typeIndex] += 1;
row.totals[anyIndex] += 1;
}
r.diff = µm.tMatrix.diff(µm.pMatrix, r.hostname, Object.keys(r.rows));
return r;
};
/******************************************************************************/
var announce = function(msg) {
// Background page handler
defaultHandler(msg, null, nullFunc);
var matrixSnapshotFromTabId = function(details, callback) {
if ( details.targetTabId ) {
callback(matrixSnapshot(details.targetTabId, details));
return;
}
// Extension pages & content scripts handlers
for ( var portName in nameToPortMap ) {
if ( nameToPortMap.hasOwnProperty(portName) === false ) {
continue;
vAPI.tabs.get(null, function(tab) {
callback(matrixSnapshot(tab.id, details));
});
};
/******************************************************************************/
var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
case 'matrixSnapshot':
matrixSnapshotFromTabId(request, callback);
return;
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'disconnected':
// https://github.com/gorhill/httpswitchboard/issues/94
if ( µm.userSettings.smartAutoReload ) {
vAPI.tabs.get(null, smartReload);
}
break;
case 'toggleMatrixSwitch':
µm.tMatrix.setSwitchZ(
request.switchName,
request.srcHostname,
µm.tMatrix.evaluateSwitchZ(request.switchName, request.srcHostname) === false
);
break;
case 'blacklistMatrixCell':
µm.tMatrix.blacklistCell(
request.srcHostname,
request.desHostname,
request.type
);
break;
case 'whitelistMatrixCell':
µm.tMatrix.whitelistCell(
request.srcHostname,
request.desHostname,
request.type
);
break;
case 'graylistMatrixCell':
µm.tMatrix.graylistCell(
request.srcHostname,
request.desHostname,
request.type
);
break;
case 'applyDiffToPermanentMatrix': // aka "persist"
if ( µm.pMatrix.applyDiff(request.diff, µm.tMatrix) ) {
µm.saveMatrix();
}
nameToPortMap[portName].postMessage({ id: -1, msg: msg });
break;
case 'applyDiffToTemporaryMatrix': // aka "revert"
µm.tMatrix.applyDiff(request.diff, µm.pMatrix);
break;
case 'revertTemporaryMatrix':
µm.tMatrix.assign(µm.pMatrix);
break;
default:
return vAPI.messaging.UNHANDLED;
}
callback(response);
};
vAPI.messaging.listen('popup.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
// content scripts
(function() {
var µm = µMatrix;
/******************************************************************************/
var onMessage = function(request, port) {
var reqId = request.id;
// Annoucement: dispatch everywhere.
if ( reqId < 0 ) {
announce(request.msg);
var contentScriptSummaryHandler = function(tabId, details) {
// TODO: Investigate "Error in response to tabs.executeScript: TypeError:
// Cannot read property 'locationURL' of null" (2013-11-12). When can this
// happens?
if ( !details || !details.locationURL ) {
return;
}
var listener = listenerFromPortName(port.name) || defaultHandler;
// Being told
if ( reqId === 0 ) {
listener(request.msg, port.sender, nullFunc);
return;
var pageURL = µm.pageUrlFromTabId(tabId);
var pageStats = µm.pageStatsFromPageUrl(pageURL);
var µmuri = µm.URI.set(details.locationURL);
var frameURL = µmuri.normalizedURI();
var frameHostname = µmuri.hostname;
var urls, url, r;
// https://github.com/gorhill/httpswitchboard/issues/333
// Look-up here whether inline scripting is blocked for the frame.
var inlineScriptBlocked = µm.mustBlock(µm.scopeFromURL(pageURL), frameHostname, 'script');
// scripts
// https://github.com/gorhill/httpswitchboard/issues/25
if ( pageStats && inlineScriptBlocked ) {
urls = details.scriptSources;
for ( url in urls ) {
if ( !urls.hasOwnProperty(url) ) {
continue;
}
// Being asked
listener(request.msg, port.sender, function(response) {
port.postMessage({
id: reqId,
msg: response !== undefined ? response : null
});
});
if ( url === '{inline_script}' ) {
url = frameURL + '{inline_script}';
}
r = µm.filterRequest(pageURL, 'script', url);
pageStats.recordRequest('script', url, r !== false, r);
}
}
// TODO: as of 2014-05-26, not sure this is needed anymore, since µMatrix
// no longer uses chrome.contentSettings API (I think that was the reason
// this code was put in).
// plugins
// https://github.com/gorhill/httpswitchboard/issues/25
if ( pageStats ) {
urls = details.pluginSources;
for ( url in urls ) {
if ( !urls.hasOwnProperty(url) ) {
continue;
}
r = µm.filterRequest(pageURL, 'plugin', url);
pageStats.recordRequest('plugin', url, r !== false, r);
}
}
// https://github.com/gorhill/httpswitchboard/issues/181
µm.onPageLoadCompleted(pageURL);
};
/******************************************************************************/
// Default is for commonly used message.
var contentScriptLocalStorageHandler = function(pageURL) {
var µmuri = µm.URI.set(pageURL);
var response = µm.mustBlock(µm.scopeFromURL(pageURL), µmuri.hostname, 'cookie');
µm.recordFromPageUrl(
pageURL,
'cookie',
µmuri.rootURL() + '/{localStorage}',
response
);
response = response && µm.userSettings.deleteLocalStorage;
if ( response ) {
µm.localStorageRemovedCounter++;
}
return response;
};
/******************************************************************************/
function defaultHandler(request, sender, callback) {
var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
case 'getAssetContent':
return µm.assets.getLocal(request.url, callback);
default:
break;
}
var tabId = sender.tab.id;
// Sync
var response;
switch ( request.what ) {
case 'contentScriptHasLocalStorage':
response = contentScriptLocalStorageHandler(request.url);
µm.updateBadgeAsync(tabId);
break;
case 'contentScriptSummary':
contentScriptSummaryHandler(tabId, request);
µm.updateBadgeAsync(tabId);
break;
case 'checkScriptBlacklisted':
response = {
scriptBlacklisted: µm.mustBlock(
µm.scopeFromURL(request.url),
µm.hostnameFromURL(request.url),
'script'
)
};
break;
case 'getUserAgentReplaceStr':
response = µm.tMatrix.evaluateSwitchZ('ua-spoof', request.hostname) ?
µm.userAgentReplaceStr :
undefined;
break;
default:
return vAPI.messaging.UNHANDLED;
}
callback(response);
};
vAPI.messaging.listen('contentscript-start.js', onMessage);
vAPI.messaging.listen('contentscript-end.js', onMessage);
/******************************************************************************/
})();
/******************************************************************************/
/******************************************************************************/
// settings.js
(function() {
var onMessage = function(request, sender, callback) {
var µm = µMatrix;
// Async
switch ( request.what ) {
default:
break;
}
@ -149,89 +511,385 @@ function defaultHandler(request, sender, callback) {
var response;
switch ( request.what ) {
case 'forceReloadTab':
µm.forceReload(request.tabId);
default:
return vAPI.messaging.UNHANDLED;
}
callback(response);
};
vAPI.messaging.listen('settings.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
// privacy.js
(function() {
var onMessage = function(request, sender, callback) {
var µm = µMatrix;
// Async
switch ( request.what ) {
default:
break;
}
case 'getUserSettings':
response = µm.userSettings;
// Sync
var response;
switch ( request.what ) {
case 'getPrivacySettings':
response = {
userSettings: µm.userSettings,
matrixSwitches: {
'https-strict': µm.pMatrix.evaluateSwitch('https-strict', '*') === 1,
'ua-spoof': µm.pMatrix.evaluateSwitch('ua-spoof', '*') === 1,
'referrer-spoof': µm.pMatrix.evaluateSwitch('referrer-spoof', '*') === 1
}
};
break;
case 'gotoExtensionURL':
µm.utils.gotoExtensionURL(request.url);
case 'setMatrixSwitch':
µm.tMatrix.setSwitch(request.switchName, '*', request.state);
if ( µm.pMatrix.setSwitch(request.switchName, '*', request.state) ) {
µm.saveMatrix();
}
break;
case 'gotoURL':
µm.utils.gotoURL(request);
default:
return vAPI.messaging.UNHANDLED;
}
callback(response);
};
vAPI.messaging.listen('privacy.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
// user-rules.js
(function() {
var µm = µMatrix;
/******************************************************************************/
var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
default:
break;
}
case 'reloadHostsFiles':
µm.reloadHostsFiles(request.switches, request.update);
// Sync
var response;
switch ( request.what ) {
case 'getUserRules':
response = {
temporaryRules: µm.tMatrix.toString(),
permanentRules: µm.pMatrix.toString()
};
break;
case 'userSettings':
response = µm.changeUserSettings(request.name, request.value);
case 'setUserRules':
if ( typeof request.temporaryRules === 'string' ) {
µm.tMatrix.fromString(request.temporaryRules);
}
if ( typeof request.permanentRules === 'string' ) {
µm.pMatrix.fromString(request.permanentRules);
µm.saveMatrix();
}
response = {
temporaryRules: µm.tMatrix.toString(),
permanentRules: µm.pMatrix.toString()
};
break;
default:
// console.error('messaging.js / defaultHandler > unknown request: %o', request);
return vAPI.messaging.UNHANDLED;
}
callback(response);
};
vAPI.messaging.listen('user-rules.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
// hosts-files.js
(function() {
var µm = µMatrix;
/******************************************************************************/
var getLists = function(callback) {
var r = {
available: null,
cache: null,
current: µm.liveHostsFiles,
blockedHostnameCount: µm.ubiquitousBlacklist.count,
autoUpdate: µm.userSettings.autoUpdate
};
var onMetadataReady = function(entries) {
r.cache = entries;
callback(r);
};
var onAvailableHostsFilesReady = function(lists) {
r.available = lists;
µm.assets.metadata(onMetadataReady);
};
µm.getAvailableHostsFiles(onAvailableHostsFilesReady);
};
/******************************************************************************/
var onMessage = function(request, sender, callback) {
var µm = µMatrix;
// Async
switch ( request.what ) {
case 'getLists':
return getLists(callback);
case 'purgeAllCaches':
return µm.assets.purgeAll(callback);
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'purgeCache':
µm.assets.purge(request.path);
break;
default:
return vAPI.messaging.UNHANDLED;
}
callback(response);
}
};
vAPI.messaging.listen('hosts-files.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
// info.js
(function() {
/******************************************************************************/
// map(pageURL) => array of request log entries
var getRequestLog = function(pageURL) {
var requestLogs = {};
var pageStores = µMatrix.pageStats;
var pageURLs = pageURL ? [pageURL] : Object.keys(pageStores);
var pageStore, pageRequestLog, logEntries, j, logEntry;
for ( var i = 0; i < pageURLs.length; i++ ) {
pageURL = pageURLs[i];
pageStore = pageStores[pageURL];
if ( !pageStore ) {
continue;
}
pageRequestLog = [];
logEntries = pageStore.requests.getLoggedRequests();
j = logEntries.length;
while ( j-- ) {
// rhill 2013-12-04: `logEntry` can be null since a ring buffer is
// now used, and it might not have been filled yet.
if ( logEntry = logEntries[j] ) {
pageRequestLog.push(logEntry);
}
}
requestLogs[pageURL] = pageRequestLog;
}
return requestLogs;
};
/******************************************************************************/
var clearRequestLog = function(pageURL) {
var pageStores = µMatrix.pageStats;
var pageURLs = pageURL ? [pageURL] : Object.keys(pageStores);
var pageStore;
for ( var i = 0; i < pageURLs.length; i++ ) {
if ( pageStore = pageStores[pageURLs[i]] ) {
pageStore.requests.clearLogBuffer();
}
}
};
/******************************************************************************/
// Port disconnected, relay this information to apropriate listener.
var onMessage = function(request, sender, callback) {
var µm = µMatrix;
// Async
switch ( request.what ) {
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'getPageURLs':
response = {
pageURLs: Object.keys(µm.pageUrlToTabId),
behindTheSceneURL: µm.behindTheSceneURL
};
break;
var onDisconnect = function(port) {
// Notify listener of the disconnection -- using a reserved message id.
var listener = listenerFromPortName(port.name) || defaultHandler;
var msg = {
'what': 'disconnected',
'which': listenerNameFromPortName(port.name)
case 'getStats':
var pageStore = µm.pageStats[request.pageURL];
response = {
globalNetStats: µm.requestStats,
pageNetStats: pageStore ? pageStore.requestStats : null,
cookieHeaderFoiledCounter: µm.cookieHeaderFoiledCounter,
refererHeaderFoiledCounter: µm.refererHeaderFoiledCounter,
hyperlinkAuditingFoiledCounter: µm.hyperlinkAuditingFoiledCounter,
cookieRemovedCounter: µm.cookieRemovedCounter,
localStorageRemovedCounter: µm.localStorageRemovedCounter,
browserCacheClearedCounter: µm.browserCacheClearedCounter
};
listener(msg, port.sender, nullFunc);
break;
case 'getRequestLogs':
response = getRequestLog(request.pageURL);
break;
// Cleanup port if no longer in use.
if ( nameToPortMap.hasOwnProperty(port.name) ) {
delete nameToPortMap[port.name];
port.onMessage.removeListener(onMessage);
port.onDisconnect.removeListener(onDisconnect);
case 'clearRequestLogs':
clearRequestLog(request.pageURL);
break;
default:
return vAPI.messaging.UNHANDLED;
}
callback(response);
};
vAPI.messaging.listen('info.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/
var onConnect = function(port) {
// We must have a port name.
if ( typeof port.name !== 'string' || port.name === '' ) {
console.error('messaging.js / onConnectHandler(): no port name!');
return;
// about.js
(function() {
var µm = µMatrix;
/******************************************************************************/
var restoreUserData = function(userData) {
var countdown = 3;
var onCountdown = function() {
countdown -= 1;
if ( countdown === 0 ) {
vAPI.app.restart();
}
};
// Ensure port name is unique
port.name += '/' + runtimeIdGenerator++;
var onAllRemoved = function() {
// Be sure to adjust `countdown` if adding/removing anything below
µm.XAL.keyvalSetMany(userData.settings, onCountdown);
µm.XAL.keyvalSetOne('userMatrix', userData.rules, onCountdown);
µm.XAL.keyvalSetOne('liveHostsFiles', userData.hostsFiles, onCountdown);
};
nameToPortMap[port.name] = port;
port.onMessage.addListener(onMessage);
port.onDisconnect.addListener(onDisconnect);
// If we are going to restore all, might as well wipe out clean local
// storage
µm.XAL.keyvalRemoveAll(onAllRemoved);
};
/******************************************************************************/
chrome.runtime.onConnect.addListener(onConnect);
var resetUserData = function() {
var onAllRemoved = function() {
vAPI.app.restart();
};
µm.XAL.keyvalRemoveAll(onAllRemoved);
};
/******************************************************************************/
return {
listen: listen,
tell: tell,
announce: announce,
defaultHandler: defaultHandler
var onMessage = function(request, sender, callback) {
// Async
switch ( request.what ) {
default:
break;
}
// Sync
var response;
switch ( request.what ) {
case 'getAllUserData':
response = {
app: 'µMatrix',
version: vAPI.app.version,
when: Date.now(),
settings: µm.userSettings,
rules: µm.pMatrix.toString(),
hostsFiles: µm.liveHostsFiles
};
break;
case 'getSomeStats':
response = {
version: vAPI.app.version,
storageUsed: µm.storageUsed
};
break;
case 'restoreAllUserData':
restoreUserData(request.userData);
break;
case 'resetAllUserData':
resetUserData();
break;
default:
return vAPI.messaging.UNHANDLED;
}
callback(response);
};
vAPI.messaging.listen('about.js', onMessage);
/******************************************************************************/
/******************************************************************************/
})();

@ -436,6 +436,8 @@ return {
µMatrix.PageStore = (function() {
'use strict';
/******************************************************************************/
var µm = µMatrix;
@ -555,20 +557,16 @@ PageStore.prototype.recordRequest = function(type, url, block) {
// notifying me, and this causes internal cached state to be out of sync.
PageStore.prototype.updateBadge = function(tabId) {
// Icon
var iconPath;
var iconId = null;
var badgeStr = '';
var total = this.perLoadAllowedRequestCount + this.perLoadBlockedRequestCount;
if ( total ) {
var squareSize = 19;
var greenSize = squareSize * Math.sqrt(this.perLoadAllowedRequestCount / total);
greenSize = greenSize < squareSize/2 ? Math.ceil(greenSize) : Math.floor(greenSize);
iconPath = 'img/browsericons/icon19-' + greenSize + '.png';
iconId = greenSize < squareSize/2 ? Math.ceil(greenSize) : Math.floor(greenSize);
badgeStr = µm.formatCount(this.distinctRequestCount);
} else {
iconPath = 'img/browsericons/icon19.png';
}
µm.XAL.setIcon(tabId, iconPath, badgeStr);
vAPI.setIcon(tabId, iconId, badgeStr);
};
/******************************************************************************/
@ -577,6 +575,8 @@ return {
factory: pageStoreFactory
};
/******************************************************************************/
})();
/******************************************************************************/

@ -19,7 +19,7 @@
Home: https://github.com/gorhill/uMatrix
*/
/* global punycode, uDom, messaging */
/* global punycode, vAPI, uDom */
/* jshint esnext: true, bitwise: false */
/******************************************************************************/
@ -27,6 +27,8 @@
(function() {
'use strict';
/******************************************************************************/
/******************************************************************************/
@ -71,8 +73,6 @@ var blacklistedHostnamesLabel = '';
// https://github.com/gorhill/httpswitchboard/issues/345
messaging.start('popup.js');
var onMessage = function(msg) {
if ( msg.what !== 'urlStatsChanged' ) {
return;
@ -83,7 +83,7 @@ var onMessage = function(msg) {
queryMatrixSnapshot(makeMenu);
};
messaging.listen(onMessage);
var messager = vAPI.messaging.channel('popup.js', onMessage);
/******************************************************************************/
/******************************************************************************/
@ -94,7 +94,7 @@ function getUserSetting(setting) {
function setUserSetting(setting, value) {
matrixSnapshot.userSettings[setting] = value;
messaging.tell({
messager.send({
what: 'userSettings',
name: setting,
value: value
@ -405,7 +405,7 @@ function handleFilter(button, leaning) {
desHostname: desHostname,
type: type
};
messaging.ask(request, updateMatrixSnapshot);
messager.send(request, updateMatrixSnapshot);
}
function handleWhitelistFilter(button) {
@ -950,7 +950,7 @@ function initMenuEnvironment() {
while ( i-- ) {
key = keys[i];
cell = uDom('#matHead .matCell[data-req-type="'+ key +'"]');
text = chrome.i18n.getMessage(key + 'PrettyName');
text = vAPI.i18n(key + 'PrettyName');
cell.text(text);
prettyNames[key] = text;
}
@ -1043,7 +1043,7 @@ function toggleMatrixSwitch() {
switchName: switchName,
srcHostname: matrixSnapshot.scope
};
messaging.ask(request, updateMatrixSnapshot);
messager.send(request, updateMatrixSnapshot);
}
/******************************************************************************/
@ -1068,7 +1068,7 @@ function persistMatrix() {
what: 'applyDiffToPermanentMatrix',
diff: matrixSnapshot.diff
};
messaging.ask(request, updateMatrixSnapshot);
messager.send(request, updateMatrixSnapshot);
}
/******************************************************************************/
@ -1081,7 +1081,7 @@ function revertMatrix() {
what: 'applyDiffToTemporaryMatrix',
diff: matrixSnapshot.diff
};
messaging.ask(request, updateMatrixSnapshot);
messager.send(request, updateMatrixSnapshot);
}
/******************************************************************************/
@ -1100,13 +1100,13 @@ function revertAll() {
var request = {
what: 'revertTemporaryMatrix'
};
messaging.ask(request, updateMatrixSnapshot);
messager.send(request, updateMatrixSnapshot);
}
/******************************************************************************/
function buttonReloadHandler() {
messaging.tell({
messager.send({
what: 'forceReloadTab',
tabId: targetTabId
});
@ -1125,9 +1125,9 @@ function mouseleaveMatrixCellHandler() {
/******************************************************************************/
function gotoExtensionURL() {
var url = this.getAttribute('data-extension-url');
var url = uDom(this).attr('data-extension-url');
if ( url ) {
messaging.tell({ what: 'gotoExtensionURL', url: url });
messager.send({ what: 'gotoExtensionURL', url: url });
}
}
@ -1136,7 +1136,7 @@ function gotoExtensionURL() {
function gotoExternalURL() {
var url = this.getAttribute('data-external-url');
if ( url ) {
messaging.tell({ what: 'gotoURL', url: url });
messager.send({ what: 'gotoURL', url: url });
}
}
@ -1152,8 +1152,6 @@ function dropDownMenuHide() {
/******************************************************************************/
// Because chrome.tabs.query() is async
var onMatrixSnapshotReady = function(response) {
// Now that tabId and pageURL are set, we can build our menu
initMenuEnvironment();
@ -1165,7 +1163,7 @@ var onMatrixSnapshotReady = function(response) {
uDom('#toolbarLeft').remove();
// https://github.com/gorhill/httpswitchboard/issues/191
uDom('#noNetTrafficPrompt').text(chrome.i18n.getMessage('matrixNoNetTrafficPrompt'));
uDom('#noNetTrafficPrompt').text(vAPI.i18n('matrixNoNetTrafficPrompt'));
uDom('#noNetTrafficPrompt').css('display', '');
}
};
@ -1183,20 +1181,7 @@ var queryMatrixSnapshot = function(callback) {
matrixSnapshot = response;
callback();
};
var onTabsReceived = function(tabs) {
if ( tabs.length === 0 ) {
return;
}
var tab = tabs[0];
request.tabId = targetTabId = tab.id;
request.tabURL = tab.url;
messaging.ask(request, snapshotReceived);
};
if ( targetTabId === undefined ) {
chrome.tabs.query({ active: true, currentWindow: true }, onTabsReceived);
} else {
messaging.ask(request, snapshotReceived);
}
messager.send(request, snapshotReceived);
};
/******************************************************************************/

@ -19,22 +19,24 @@
Home: https://github.com/gorhill/uMatrix
*/
/* global messaging, uDom */
/* global vAPI, uDom */
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
messaging.start('privacy.js');
var messager = vAPI.messaging.channel('privacy.js');
var cachedPrivacySettings = {};
/******************************************************************************/
function changeUserSettings(name, value) {
messaging.tell({
messager.send({
what: 'userSettings',
name: name,
value: value
@ -44,7 +46,7 @@ function changeUserSettings(name, value) {
/******************************************************************************/
function changeMatrixSwitch(name, state) {
messaging.tell({
messager.send({
what: 'setMatrixSwitch',
switchName: name,
state: state
@ -141,7 +143,7 @@ uDom.onLoad(function() {
installEventHandlers();
};
messaging.ask({ what: 'getPrivacySettings' }, onSettingsReceived);
messager.send({ what: 'getPrivacySettings' }, onSettingsReceived);
});
/******************************************************************************/

@ -19,16 +19,18 @@
Home: https://github.com/gorhill/uMatrix
*/
/* global messaging, uDom */
/* global vAPI, uDom */
/* jshint multistr: true */
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
messaging.start('settings.js');
var messager = vAPI.messaging.channel('settings.js');
var cachedUserSettings = {};
@ -68,7 +70,7 @@ var onSubframeColorChanged = function() {
/******************************************************************************/
function changeUserSettings(name, value) {
messaging.tell({
messager.send({
what: 'userSettings',
name: name,
value: value
@ -126,7 +128,7 @@ uDom.onLoad(function() {
installEventHandlers();
};
messaging.ask({ what: 'getUserSettings' }, onUserSettingsReceived);
messager.send({ what: 'getUserSettings' }, onUserSettingsReceived);
});
/******************************************************************************/

@ -43,85 +43,6 @@
/******************************************************************************/
function onTabCreated(tab) {
// Can this happen?
if ( tab.id < 0 || !tab.url || tab.url === '' ) {
return;
}
// https://github.com/gorhill/httpswitchboard/issues/303
// This takes care of rebinding the tab to the proper page store
// when the user navigate back in his history.
µMatrix.bindTabToPageStats(tab.id, tab.url);
}
chrome.tabs.onCreated.addListener(onTabCreated);
/******************************************************************************/
function onTabUpdated(tabId, changeInfo, tab) {
// Can this happen?
if ( !tab.url || tab.url === '' ) {
return;
}
// https://github.com/gorhill/httpswitchboard/issues/303
// This takes care of rebinding the tab to the proper page store
// when the user navigate back in his history.
if ( changeInfo.url ) {
µMatrix.bindTabToPageStats(tabId, tab.url, 'pageUpdated');
}
// rhill 2013-12-23: Compute state after whole page is loaded. This is
// better than building a state snapshot dynamically when requests are
// recorded, because here we are not afflicted by the browser cache
// mechanism.
// rhill 2014-03-05: Use tab id instead of page URL: this allows a
// blocked page using µMatrix internal data URI-based page to be properly
// unblocked when user un-blacklist the hostname.
// https://github.com/gorhill/httpswitchboard/issues/198
if ( changeInfo.status === 'complete' ) {
var pageStats = µMatrix.pageStatsFromTabId(tabId);
if ( pageStats ) {
pageStats.state = µMatrix.computeTabState(tabId);
}
}
}
chrome.tabs.onUpdated.addListener(onTabUpdated);
/******************************************************************************/
function onTabRemoved(tabId) {
// Can this happen?
if ( tabId < 0 ) {
return;
}
µMatrix.unbindTabFromPageStats(tabId);
}
chrome.tabs.onRemoved.addListener(onTabRemoved);
/******************************************************************************/
// Bind a top URL to a specific tab
function onBeforeNavigateCallback(details) {
// Don't bind to a subframe
if ( details.frameId > 0 ) {
return;
}
// console.debug('onBeforeNavigateCallback() > "%s" = %o', details.url, details);
µMatrix.bindTabToPageStats(details.tabId, details.url);
}
chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigateCallback);
/******************************************************************************/
// Browser data jobs
(function() {
@ -136,8 +57,8 @@ chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigateCallback);
}
µm.clearBrowserCacheCycle = µm.userSettings.clearBrowserCacheAfter;
µm.browserCacheClearedCounter++;
chrome.browsingData.removeCache({ since: 0 });
// console.debug('clearBrowserCacheCallback()> chrome.browsingData.removeCache() called');
vAPI.browserCache.clearByTime(0);
// console.debug('clearBrowserCacheCallback()> vAPI.browserCache.clearByTime() called');
};
µMatrix.asyncJobs.add('clearBrowserCache', null, jobCallback, 15 * 60 * 1000, true);
@ -177,13 +98,14 @@ chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigateCallback);
var i = tabs.length;
// console.debug('start.js > binding %d tabs', i);
while ( i-- ) {
µm.tabContextManager.commit(tabs[i].id, tabs[i].url);
µm.bindTabToPageStats(tabs[i].id, tabs[i].url);
}
µm.webRequest.start();
};
var queryTabs = function() {
chrome.tabs.query({ url: '<all_urls>' }, bindTabs);
vAPI.tabs.getAll(bindTabs);
};
µm.load(queryTabs);

@ -28,7 +28,7 @@
var getBytesInUseHandler = function(bytesInUse) {
µm.storageUsed = bytesInUse;
};
chrome.storage.local.getBytesInUse(null, getBytesInUseHandler);
vAPI.storage.getBytesInUse(null, getBytesInUseHandler);
};
/******************************************************************************/
@ -71,7 +71,7 @@
callback(µm.userSettings);
};
chrome.storage.local.get(this.userSettings, settingsLoaded);
vAPI.storage.get(this.userSettings, settingsLoaded);
};
/******************************************************************************/
@ -157,7 +157,7 @@
}
// Now get user's selection of lists
chrome.storage.local.get(
vAPI.storage.get(
{ 'liveHostsFiles': availableHostsFiles },
onSelectedHostsFilesLoaded
);
@ -208,8 +208,8 @@
var loadHostsFilesEnd = function() {
µm.ubiquitousBlacklist.freeze();
chrome.storage.local.set({ 'liveHostsFiles': µm.liveHostsFiles });
µm.messaging.announce({ what: 'loadHostsFilesCompleted' });
vAPI.storage.set({ 'liveHostsFiles': µm.liveHostsFiles });
vAPI.messaging.broadcast({ what: 'loadHostsFilesCompleted' });
callback();
};
@ -343,7 +343,7 @@
}
// Save switch states
chrome.storage.local.set(
vAPI.storage.set(
{ 'liveHostsFiles': liveHostsFiles },
this.loadUpdatableAssets.bind(this, update)
);

@ -21,11 +21,407 @@
/* global chrome, µMatrix */
/******************************************************************************/
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
var µm = µMatrix;
// https://github.com/gorhill/httpswitchboard/issues/303
// Some kind of trick going on here:
// Any scheme other than 'http' and 'https' is remapped into a fake
// URL which trick the rest of µMatrix into being able to process an
// otherwise unmanageable scheme. µMatrix needs web page to have a proper
// hostname to work properly, so just like the 'chromium-behind-the-scene'
// fake domain name, we map unknown schemes into a fake '{scheme}-scheme'
// hostname. This way, for a specific scheme you can create scope with
// rules which will apply only to that scheme.
/******************************************************************************/
/******************************************************************************/
µm.normalizePageURL = function(tabId, pageURL) {
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return 'http://behind-the-scene/';
}
var uri = this.URI.set(pageURL);
var scheme = uri.scheme;
if ( scheme === 'https' || scheme === 'http' ) {
return uri.normalizedURI();
}
var url = 'http://' + scheme + '-scheme/';
if ( uri.hostname !== '' ) {
url += uri.hostname + '/';
}
return url;
};
/******************************************************************************/
/******************************************************************************
To keep track from which context *exactly* network requests are made. This is
often tricky for various reasons, and the challenge is not specific to one
browser.
The time at which a URL is assigned to a tab and the time when a network
request for a root document is made must be assumed to be unrelated: it's all
asynchronous. There is no guaranteed order in which the two events are fired.
Also, other "anomalies" can occur:
- a network request for a root document is fired without the corresponding
tab being really assigned a new URL
<https://github.com/chrisaljoudi/uBlock/issues/516>
- a network request for a secondary resource is labeled with a tab id for
which no root document was pulled for that tab.
<https://github.com/chrisaljoudi/uBlock/issues/1001>
- a network request for a secondary resource is made without the root
document to which it belongs being formally bound yet to the proper tab id,
causing a bad scope to be used for filtering purpose.
<https://github.com/chrisaljoudi/uBlock/issues/1205>
<https://github.com/chrisaljoudi/uBlock/issues/1140>
So the solution here is to keep a lightweight data structure which only
purpose is to keep track as accurately as possible of which root document
belongs to which tab. That's the only purpose, and because of this, there are
no restrictions for when the URL of a root document can be associated to a tab.
Before, the PageStore object was trying to deal with this, but it had to
enforce some restrictions so as to not descend into one of the above issues, or
other issues. The PageStore object can only be associated with a tab for which
a definitive navigation event occurred, because it collects information about
what occurred in the tab (for example, the number of requests blocked for a
page).
The TabContext objects do not suffer this restriction, and as a result they
offer the most reliable picture of which root document URL is really associated
to which tab. Moreover, the TabObject can undo an association from a root
document, and automatically re-associate with the next most recent. This takes
care of <https://github.com/chrisaljoudi/uBlock/issues/516>.
The PageStore object no longer cache the various information about which
root document it is currently bound. When it needs to find out, it will always
defer to the TabContext object, which will provide the real answer. This takes
case of <https://github.com/chrisaljoudi/uBlock/issues/1205>. In effect, the
master switch and dynamic filtering rules can be evaluated now properly even
in the absence of a PageStore object, this was not the case before.
Also, the TabContext object will try its best to find a good candidate root
document URL for when none exists. This takes care of
<https://github.com/chrisaljoudi/uBlock/issues/1001>.
The TabContext manager is self-contained, and it takes care to properly
housekeep itself.
*/
µm.tabContextManager = (function() {
var tabContexts = Object.create(null);
// https://github.com/chrisaljoudi/uBlock/issues/1001
// This is to be used as last-resort fallback in case a tab is found to not
// be bound while network requests are fired for the tab.
var mostRecentRootDocURL = '';
var mostRecentRootDocURLTimestamp = 0;
var gcPeriod = 10 * 60 * 1000;
var TabContext = function(tabId) {
this.tabId = tabId;
this.stack = [];
this.rawURL =
this.normalURL =
this.rootHostname =
this.rootDomain = '';
this.timer = null;
this.onTabCallback = null;
this.onTimerCallback = null;
tabContexts[tabId] = this;
};
TabContext.prototype.destroy = function() {
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
return;
}
if ( this.timer !== null ) {
clearTimeout(this.timer);
this.timer = null;
}
delete tabContexts[this.tabId];
};
TabContext.prototype.onTab = function(tab) {
if ( tab ) {
this.timer = setTimeout(this.onTimerCallback, gcPeriod);
} else {
this.destroy();
}
};
TabContext.prototype.onTimer = function() {
this.timer = null;
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
return;
}
vAPI.tabs.get(this.tabId, this.onTabCallback);
};
// This takes care of orphanized tab contexts. Can't be started for all
// contexts, as the behind-the-scene context is permanent -- so we do not
// want to slush it.
TabContext.prototype.autodestroy = function() {
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
return;
}
this.onTabCallback = this.onTab.bind(this);
this.onTimerCallback = this.onTimer.bind(this);
this.timer = setTimeout(this.onTimerCallback, gcPeriod);
};
// Update just force all properties to be updated to match the most current
// root URL.
TabContext.prototype.update = function() {
if ( this.stack.length === 0 ) {
this.rawURL = this.normalURL = this.rootHostname = this.rootDomain = '';
} else {
this.rawURL = this.stack[this.stack.length - 1];
this.normalURL = µm.normalizePageURL(this.tabId, this.rawURL);
this.rootHostname = µm.URI.hostnameFromURI(this.normalURL);
this.rootDomain = µm.URI.domainFromHostname(this.rootHostname);
}
};
// Called whenever a candidate root URL is spotted for the tab.
TabContext.prototype.push = function(url) {
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
return;
}
this.stack.push(url);
this.update();
};
// Called when a former push is a false positive:
// https://github.com/chrisaljoudi/uBlock/issues/516
TabContext.prototype.unpush = function(url) {
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
return;
}
// We are not going to unpush if there is no other candidate, the
// point of unpush is to make space for a better candidate.
if ( this.stack.length === 1 ) {
return;
}
var pos = this.stack.indexOf(url);
if ( pos === -1 ) {
return;
}
this.stack.splice(pos, 1);
if ( this.stack.length === 0 ) {
this.destroy();
return;
}
if ( pos !== this.stack.length ) {
return;
}
this.update();
};
// This tells that the url is definitely the one to be associated with the
// tab, there is no longer any ambiguity about which root URL is really
// sitting in which tab.
TabContext.prototype.commit = function(url) {
if ( vAPI.isBehindTheSceneTabId(this.tabId) ) {
return;
}
this.stack = [url];
this.update();
};
// These are to be used for the API of the tab context manager.
var push = function(tabId, url) {
var entry = tabContexts[tabId];
if ( entry === undefined ) {
entry = new TabContext(tabId);
entry.autodestroy();
}
entry.push(url);
mostRecentRootDocURL = url;
mostRecentRootDocURLTimestamp = Date.now();
return entry;
};
// Find a tab context for a specific tab. If none is found, attempt to
// fix this. When all fail, the behind-the-scene context is returned.
var lookup = function(tabId, url) {
var entry;
if ( url !== undefined ) {
entry = push(tabId, url);
} else {
entry = tabContexts[tabId];
}
if ( entry !== undefined ) {
return entry;
}
// https://github.com/chrisaljoudi/uBlock/issues/1025
// Google Hangout popup opens without a root frame. So for now we will
// just discard that best-guess root frame if it is too far in the
// future, at which point it ceases to be a "best guess".
if ( mostRecentRootDocURL !== '' && mostRecentRootDocURLTimestamp + 500 < Date.now() ) {
mostRecentRootDocURL = '';
}
// https://github.com/chrisaljoudi/uBlock/issues/1001
// Not a behind-the-scene request, yet no page store found for the
// tab id: we will thus bind the last-seen root document to the
// unbound tab. It's a guess, but better than ending up filtering
// nothing at all.
if ( mostRecentRootDocURL !== '' ) {
return push(tabId, mostRecentRootDocURL);
}
// If all else fail at finding a page store, re-categorize the
// request as behind-the-scene. At least this ensures that ultimately
// the user can still inspect/filter those net requests which were
// about to fall through the cracks.
// Example: Chromium + case #12 at
// http://raymondhill.net/ublock/popup.html
return tabContexts[vAPI.noTabId];
};
var commit = function(tabId, url) {
var entry = tabContexts[tabId];
if ( entry === undefined ) {
entry = push(tabId, url);
} else {
entry.commit(url);
}
return entry;
};
var unpush = function(tabId, url) {
var entry = tabContexts[tabId];
if ( entry !== undefined ) {
entry.unpush(url);
}
};
var exists = function(tabId) {
return tabContexts[tabId] !== undefined;
};
// Behind-the-scene tab context
(function() {
var entry = new TabContext(vAPI.noTabId);
entry.stack.push('');
entry.rawURL = '';
entry.normalURL = µm.normalizePageURL(entry.tabId);
entry.rootHostname = µm.URI.hostnameFromURI(entry.normalURL);
entry.rootDomain = µm.URI.domainFromHostname(entry.rootHostname);
})();
// Context object, typically to be used to feed filtering engines.
var Context = function(tabId) {
var tabContext = lookup(tabId);
this.rootHostname = tabContext.rootHostname;
this.rootDomain = tabContext.rootDomain;
this.pageHostname =
this.pageDomain =
this.requestURL =
this.requestHostname =
this.requestDomain = '';
};
var createContext = function(tabId) {
return new Context(tabId);
};
return {
push: push,
unpush: unpush,
commit: commit,
lookup: lookup,
exists: exists,
createContext: createContext
};
})();
/******************************************************************************/
/******************************************************************************/
// When the DOM content of root frame is loaded, this means the tab
// content has changed.
vAPI.tabs.onNavigation = function(details) {
if ( details.frameId !== 0 ) {
return;
}
var tabContext = µm.tabContextManager.commit(details.tabId, details.url);
var pageStore = µm.bindTabToPageStats(details.tabId, 'afterNavigate');
};
/******************************************************************************/
// It may happen the URL in the tab changes, while the page's document
// stays the same (for instance, Google Maps). Without this listener,
// the extension icon won't be properly refreshed.
vAPI.tabs.onUpdated = function(tabId, changeInfo, tab) {
if ( !tab.url || tab.url === '' ) {
return;
}
if ( changeInfo.url ) {
µm.tabContextManager.commit(tabId, changeInfo.url);
µm.bindTabToPageStats(tabId, 'tabUpdated');
}
// rhill 2013-12-23: Compute state after whole page is loaded. This is
// better than building a state snapshot dynamically when requests are
// recorded, because here we are not afflicted by the browser cache
// mechanism.
// rhill 2014-03-05: Use tab id instead of page URL: this allows a
// blocked page using µMatrix internal data URI-based page to be properly
// unblocked when user un-blacklist the hostname.
// https://github.com/gorhill/httpswitchboard/issues/198
if ( changeInfo.status === 'complete' ) {
var pageStats = µm.pageStatsFromTabId(tabId);
if ( pageStats ) {
pageStats.state = µm.computeTabState(tabId);
}
}
};
/******************************************************************************/
vAPI.tabs.onClosed = function(tabId) {
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
return;
}
µm.unbindTabFromPageStats(tabId);
};
/******************************************************************************/
vAPI.tabs.registerListeners();
/******************************************************************************/
/******************************************************************************/
// Create a new page url stats store (if not already present)
µMatrix.createPageStore = function(pageURL) {
µm.createPageStore = function(pageURL) {
// https://github.com/gorhill/httpswitchboard/issues/303
// At this point, the URL has been page-URL-normalized
@ -57,46 +453,31 @@
/******************************************************************************/
// https://github.com/gorhill/httpswitchboard/issues/303
// Some kind of trick going on here:
// Any scheme other than 'http' and 'https' is remapped into a fake
// URL which trick the rest of µMatrix into being able to process an
// otherwise unmanageable scheme. µMatrix needs web pages to have a proper
// hostname to work properly, so just like the 'chromium-behind-the-scene'
// fake domain name, we map unknown schemes into a fake '{scheme}-scheme'
// hostname. This way, for a specific scheme you can create scope with
// rules which will apply only to that scheme.
// Create an entry for the tab if it doesn't exist
µMatrix.normalizePageURL = function(pageURL) {
var uri = this.URI.set(pageURL);
if ( uri.scheme === 'https' || uri.scheme === 'http' ) {
return uri.normalizedURI();
}
// If it is a scheme-based page URL, it is important it is crafted as a
// normalized URL just like above.
if ( uri.scheme !== '' ) {
return 'http://' + uri.scheme + '-scheme/';
µm.bindTabToPageStats = function(tabId, context) {
if ( vAPI.isBehindTheSceneTabId(tabId) === false ) {
this.updateBadgeAsync(tabId);
}
return '';
};
/******************************************************************************/
// Do not create a page store for URLs which are of no interests
if ( µm.tabContextManager.exists(tabId) === false ) {
this.unbindTabFromPageStats(tabId);
return null;
}
// Create an entry for the tab if it doesn't exist
var tabContext = µm.tabContextManager.lookup(tabId);
var rawURL = tabContext.rawURL;
µMatrix.bindTabToPageStats = function(tabId, pageURL, context) {
// https://github.com/gorhill/httpswitchboard/issues/303
// Don't rebind pages blocked by µMatrix.
var blockedRootFramePrefix = this.webRequest.blockedRootFramePrefix;
if ( pageURL.slice(0, blockedRootFramePrefix.length) === blockedRootFramePrefix ) {
if ( rawURL.lastIndexOf(blockedRootFramePrefix, 0) === 0 ) {
return null;
}
var pageStore;
// https://github.com/gorhill/httpswitchboard/issues/303
// Normalize to a page-URL.
pageURL = this.normalizePageURL(pageURL);
var pageURL = tabContext.normalURL;
// The previous page URL, if any, associated with the tab
if ( this.tabIdToPageUrl.hasOwnProperty(tabId) ) {
@ -159,7 +540,7 @@
/******************************************************************************/
µMatrix.unbindTabFromPageStats = function(tabId) {
µm.unbindTabFromPageStats = function(tabId) {
if ( this.tabIdToPageUrl.hasOwnProperty(tabId) === false ) {
return;
}
@ -179,7 +560,7 @@
// Log a request
µMatrix.recordFromTabId = function(tabId, type, url, blocked) {
µm.recordFromTabId = function(tabId, type, url, blocked) {
var pageStats = this.pageStatsFromTabId(tabId);
if ( pageStats ) {
pageStats.recordRequest(type, url, blocked);
@ -187,7 +568,7 @@
}
};
µMatrix.recordFromPageUrl = function(pageUrl, type, url, blocked, reason) {
µm.recordFromPageUrl = function(pageUrl, type, url, blocked, reason) {
var pageStats = this.pageStatsFromPageUrl(pageUrl);
if ( pageStats ) {
pageStats.recordRequest(type, url, blocked, reason);
@ -196,7 +577,7 @@
/******************************************************************************/
µMatrix.onPageLoadCompleted = function(pageURL) {
µm.onPageLoadCompleted = function(pageURL) {
var pageStats = this.pageStatsFromPageUrl(pageURL);
if ( !pageStats ) {
return;
@ -210,9 +591,9 @@
/******************************************************************************/
// Reload content of a tabs.
// Reload content of one or more tabs.
µMatrix.smartReloadTabs = function(which, tabId) {
µm.smartReloadTabs = function(which, tabId) {
if ( which === 'none' ) {
return;
}
@ -224,7 +605,6 @@
// which === 'all'
var reloadTabs = function(chromeTabs) {
var µm = µMatrix;
var tabId;
var i = chromeTabs.length;
while ( i-- ) {
@ -236,7 +616,7 @@
};
var getTabs = function() {
chrome.tabs.query({ status: 'complete' }, reloadTabs);
vAPI.tabs.getAll(reloadTabs);
};
this.asyncJobs.add('smartReloadTabs', null, getTabs, 500);
@ -246,7 +626,7 @@
// Reload content of a tab
µMatrix.smartReloadTab = function(tabId) {
µm.smartReloadTab = function(tabId) {
var pageStats = this.pageStatsFromTabId(tabId);
if ( !pageStats ) {
//console.error('HTTP Switchboard> µMatrix.smartReloadTab(): page stats for tab id %d not found', tabId);
@ -304,7 +684,7 @@
// console.log('old state: %o\nnew state: %o', oldState, newState);
if ( mustReload ) {
chrome.tabs.reload(tabId);
vAPI.tabs.reload(tabId);
}
// pageStats.state = newState;
};
@ -317,13 +697,13 @@
// `chrome-devtools://devtools/devtools.html`
// etc.
µMatrix.tabExists = function(tabId) {
µm.tabExists = function(tabId) {
return !!this.pageUrlFromTabId(tabId);
};
/******************************************************************************/
µMatrix.computeTabState = function(tabId) {
µm.computeTabState = function(tabId) {
var pageStats = this.pageStatsFromTabId(tabId);
if ( !pageStats ) {
//console.error('tab.js > µMatrix.computeTabState(): page stats for tab id %d not found', tabId);
@ -358,18 +738,18 @@
/******************************************************************************/
µMatrix.pageUrlFromTabId = function(tabId) {
µm.pageUrlFromTabId = function(tabId) {
return this.tabIdToPageUrl[tabId];
};
µMatrix.pageUrlFromPageStats = function(pageStats) {
µm.pageUrlFromPageStats = function(pageStats) {
if ( pageStats ) {
return pageStats.pageUrl;
}
return undefined;
};
µMatrix.pageStatsFromTabId = function(tabId) {
µm.pageStatsFromTabId = function(tabId) {
var pageUrl = this.tabIdToPageUrl[tabId];
if ( pageUrl ) {
return this.pageStats[pageUrl];
@ -377,7 +757,7 @@
return undefined;
};
µMatrix.pageStatsFromPageUrl = function(pageURL) {
µm.pageStatsFromPageUrl = function(pageURL) {
if ( pageURL ) {
return this.pageStats[this.normalizePageURL(pageURL)];
}
@ -386,7 +766,7 @@
/******************************************************************************/
µMatrix.resizeLogBuffers = function(size) {
µm.resizeLogBuffers = function(size) {
var pageStores = this.pageStats;
for ( var pageURL in pageStores ) {
if ( pageStores.hasOwnProperty(pageURL) ) {
@ -397,15 +777,14 @@
/******************************************************************************/
µMatrix.forceReload = function(tabId) {
chrome.tabs.reload(tabId, { bypassCache: true });
µm.forceReload = function(tabId) {
vAPI.tabs.reload(tabId, { bypassCache: true });
};
/******************************************************************************/
// Garbage collect stale url stats entries
(function() {
var µm = µMatrix;
var gcPageStats = function() {
var pageStore;
var now = Date.now();
@ -447,7 +826,7 @@
// Time somewhat arbitrary: If a web page has not been in a tab
// for some time minutes, flush its stats.
µMatrix.asyncJobs.add(
µm.asyncJobs.add(
'gcPageStats',
null,
gcPageStats,
@ -455,3 +834,7 @@
true
);
})();
/******************************************************************************/
})();

@ -197,15 +197,15 @@ var onBeforeChromeExtensionRequestHandler = function(details) {
var onBeforeRootFrameRequestHandler = function(details) {
var µm = µMatrix;
// Do not ignore traffic outside tabs
var requestURL = details.url;
var tabId = details.tabId;
if ( tabId < 0 ) {
µm.tabContextManager.push(tabId, requestURL);
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
tabId = µm.behindTheSceneTabId;
}
// It's a root frame, bind to a new page store
else {
µm.bindTabToPageStats(tabId, details.url);
} else {
µm.bindTabToPageStats(tabId);
}
var uri = µm.URI.set(details.url);
@ -213,7 +213,6 @@ var onBeforeRootFrameRequestHandler = function(details) {
return;
}
var requestURL = uri.normalizedURI();
var requestHostname = uri.hostname;
var pageStore = µm.pageStatsFromTabId(tabId);
@ -730,10 +729,6 @@ var onSubDocHeadersReceived = function(details) {
/******************************************************************************/
// As per Chrome API doc, webRequest.onErrorOccurred event is the last
// one called in the sequence of webRequest events.
// http://developer.chrome.com/extensions/webRequest.html
var onErrorOccurredHandler = function(details) {
// console.debug('onErrorOccurred()> "%s": %o', details.url, details);
var requestType = requestTypeNormalizer[details.type];
@ -798,68 +793,54 @@ var requestTypeNormalizer = {
/******************************************************************************/
var start = function() {
chrome.webRequest.onBeforeRequest.addListener(
//function(details) {
// quickProfiler.start('onBeforeRequest');
// var r = onBeforeRequestHandler(details);
// quickProfiler.stop();
// return r;
//},
onBeforeRequestHandler,
{
"urls": [
vAPI.net.onBeforeRequest = {
urls: [
"http://*/*",
"https://*/*",
"chrome-extension://*/*"
],
"types": [
"main_frame",
"sub_frame",
'stylesheet',
"script",
"image",
"object",
"xmlhttprequest",
"other"
]
},
[ "blocking" ]
);
//console.log('µMatrix > Beginning to intercept net requests at %s', (new Date()).toISOString());
extra: [ 'blocking' ],
callback: onBeforeRequestHandler
};
chrome.webRequest.onBeforeSendHeaders.addListener(
onBeforeSendHeadersHandler,
{
'urls': [
vAPI.net.onBeforeSendHeaders = {
urls: [
"http://*/*",
"https://*/*"
]
},
['blocking', 'requestHeaders']
);
],
types: [
"main_frame",
"sub_frame"
],
extra: [ 'blocking', 'requestHeaders' ],
callback: onBeforeSendHeadersHandler
};
chrome.webRequest.onHeadersReceived.addListener(
onHeadersReceived,
{
'urls': [
vAPI.net.onHeadersReceived = {
urls: [
"http://*/*",
"https://*/*"
]
},
['blocking', 'responseHeaders']
);
],
types: [
"main_frame",
"sub_frame"
],
extra: [ 'blocking', 'responseHeaders' ],
callback: onHeadersReceived
};
chrome.webRequest.onErrorOccurred.addListener(
onErrorOccurredHandler,
{
'urls': [
vAPI.net.onErrorOccurred = {
urls: [
"http://*/*",
"https://*/*"
]
}
);
],
callback: onErrorOccurredHandler
};
/******************************************************************************/
var start = function() {
vAPI.net.registerListeners();
};
/******************************************************************************/

@ -1,6 +1,6 @@
/*******************************************************************************
µBlock - a Chromium browser extension to block requests.
µBlock - a browser extension to block requests.
Copyright (C) 2014 Raymond Hill
This program is free software: you can redistribute it and/or modify
@ -19,7 +19,9 @@
Home: https://github.com/gorhill/uBlock
*/
/******************************************************************************/
/* global DOMTokenList */
/* exported uDom */
/******************************************************************************/
// It's just a silly, minimalist DOM framework: this allows me to not rely
@ -30,6 +32,8 @@
var uDom = (function() {
'use strict';
/******************************************************************************/
var DOMList = function() {
@ -136,7 +140,7 @@ var addHTMLToList = function(list, html) {
var cTag = matches[1];
var pTag = pTagOfChildTag[cTag] || 'div';
var p = document.createElement(pTag);
p.innerHTML = html;
vAPI.insertHTML(p, html);
// Find real parent
var c = p.querySelector(cTag);
p = c.parentNode;
@ -222,6 +226,12 @@ DOMList.prototype.toArray = function() {
/******************************************************************************/
DOMList.prototype.pop = function() {
return addNodeToList(new DOMList(), this.nodes.pop());
};
/******************************************************************************/
DOMList.prototype.forEach = function(fn) {
var n = this.nodes.length;
for ( var i = 0; i < n; i++ ) {
@ -479,6 +489,28 @@ DOMList.prototype.clone = function(notDeep) {
/******************************************************************************/
DOMList.prototype.nthOfType = function() {
if ( this.nodes.length === 0 ) {
return 0;
}
var node = this.nodes[0];
var tagName = node.tagName;
var i = 1;
while ( node.previousElementSibling !== null ) {
node = node.previousElementSibling;
if ( typeof node.tagName !== 'string' ) {
continue;
}
if ( node.tagName !== tagName ) {
continue;
}
i++;
}
return i;
};
/******************************************************************************/
DOMList.prototype.attr = function(attr, value) {
var i = this.nodes.length;
if ( value === undefined && typeof attr !== 'object' ) {
@ -543,7 +575,7 @@ DOMList.prototype.html = function(html) {
return i ? this.nodes[0].innerHTML : '';
}
while ( i-- ) {
this.nodes[i].innerHTML = html;
vAPI.insertHTML(this.nodes[i], html);
}
return this;
};
@ -637,6 +669,24 @@ DOMList.prototype.toggleClasses = function(classNames, targetState) {
/******************************************************************************/
var listenerEntries = [];
var ListenerEntry = function(target, type, capture, callback) {
this.target = target;
this.type = type;
this.capture = capture;
this.callback = callback;
target.addEventListener(type, callback, capture);
};
ListenerEntry.prototype.dispose = function() {
this.target.removeEventListener(this.type, this.callback, this.capture);
this.target = null;
this.callback = null;
};
/******************************************************************************/
var makeEventHandler = function(selector, callback) {
return function(event) {
var dispatcher = event.currentTarget;
@ -660,7 +710,7 @@ DOMList.prototype.on = function(etype, selector, callback) {
var i = this.nodes.length;
while ( i-- ) {
this.nodes[i].addEventListener(etype, callback, selector !== undefined);
listenerEntries.push(new ListenerEntry(this.nodes[i], etype, selector !== undefined, callback));
}
return this;
};
@ -691,6 +741,20 @@ DOMList.prototype.trigger = function(etype) {
/******************************************************************************/
// Cleanup
var onBeforeUnload = function() {
var entry;
while ( entry = listenerEntries.pop() ) {
entry.dispose();
}
window.removeEventListener('beforeunload', onBeforeUnload);
};
window.addEventListener('beforeunload', onBeforeUnload);
/******************************************************************************/
return DOMListFactory;
})();

@ -31,10 +31,10 @@ Naming convention from https://en.wikipedia.org/wiki/URI_scheme#Examples
/******************************************************************************/
// This will inserted as a module in the µMatrix object.
µMatrix.URI = (function() {
'use strict';
/******************************************************************************/
// Favorite regex tool: http://regex101.com/
@ -49,6 +49,7 @@ var reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/;
// Derived
var reSchemeFromURI = /^[^:\/?#]+:/;
var reAuthorityFromURI = /^(?:[^:\/?#]+:)?(\/\/[^\/?#]+)/;
var reCommonHostnameFromURL = /^https?:\/\/([0-9a-z_][0-9a-z._-]*[0-9a-z])\//;
// These are to parse authority field, not parsed by above official regex
// IPv6 is seen as an exception: a non-compatible IPv6 is first tried, and
@ -248,7 +249,11 @@ URI.authorityFromURI = function(uri) {
// The most used function, so it better be fast.
URI.hostnameFromURI = function(uri) {
var matches = reAuthorityFromURI.exec(uri);
var matches = reCommonHostnameFromURL.exec(uri);
if ( matches ) {
return matches[1];
}
matches = reAuthorityFromURI.exec(uri);
if ( !matches ) {
return '';
}
@ -274,21 +279,98 @@ URI.hostnameFromURI = function(uri) {
/******************************************************************************/
URI.domainFromHostname = function(hostname) {
// Try to skip looking up the PSL database
if ( domainCache.hasOwnProperty(hostname) ) {
var entry = domainCache[hostname];
entry.tstamp = Date.now();
return entry.domain;
}
// Meh.. will have to search it
if ( reIPAddressNaive.test(hostname) === false ) {
return domainCacheAdd(hostname, psl.getDomain(hostname));
}
return domainCacheAdd(hostname, hostname);
};
URI.domain = function() {
return this.domainFromHostname(this.hostname);
};
// It is expected that there is higher-scoped `publicSuffixList` lingering
// somewhere. Cache it. See <https://github.com/gorhill/publicsuffixlist.js>.
var psl = publicSuffixList;
URI.domainFromHostname = function(hostname) {
if ( !reIPAddressNaive.test(hostname) ) {
return psl.getDomain(hostname);
/******************************************************************************/
// Trying to alleviate the worries of looking up too often the domain name from
// a hostname. With a cache, uBlock benefits given that it deals with a
// specific set of hostnames within a narrow time span -- in other words, I
// believe probability of cache hit are high in uBlock.
var DomainCacheEntry = function(domain) {
this.init(domain);
};
DomainCacheEntry.prototype.init = function(domain) {
this.domain = domain;
this.tstamp = Date.now();
return this;
};
DomainCacheEntry.prototype.dispose = function() {
this.domain = '';
if ( domainCacheEntryJunkyard.length < 25 ) {
domainCacheEntryJunkyard.push(this);
}
return hostname;
};
URI.domain = function() {
return this.domainFromHostname(this.hostname);
var domainCacheEntryFactory = function(domain) {
var entry = domainCacheEntryJunkyard.pop();
if ( entry ) {
return entry.init(domain);
}
return new DomainCacheEntry(domain);
};
var domainCacheEntryJunkyard = [];
var domainCacheAdd = function(hostname, domain) {
if ( domainCache.hasOwnProperty(hostname) ) {
domainCache[hostname].tstamp = Date.now();
} else {
domainCache[hostname] = domainCacheEntryFactory(domain);
domainCacheCount += 1;
if ( domainCacheCount === domainCacheCountHighWaterMark ) {
domainCachePrune();
}
}
return domain;
};
var domainCacheEntrySort = function(a, b) {
return b.tstamp - a.tstamp;
};
var domainCachePrune = function() {
var hostnames = Object.keys(domainCache)
.sort(domainCacheEntrySort)
.slice(domainCacheCountLowWaterMark);
var i = hostnames.length;
domainCacheCount -= i;
var hostname;
while ( i-- ) {
hostname = hostnames[i];
domainCache[hostname].dispose();
delete domainCache[hostname];
}
};
var domainCache = {};
var domainCacheCount = 0;
var domainCacheCountLowWaterMark = 75;
var domainCacheCountHighWaterMark = 100;
/******************************************************************************/
URI.domainFromURI = function(uri) {

@ -19,15 +19,17 @@
Home: https://github.com/gorhill/uMatrix
*/
/* global chrome, messaging, uDom */
/* global vAPI, uDom */
/******************************************************************************/
(function() {
'use strict';
/******************************************************************************/
messaging.start('user-rules.js');
var messager = vAPI.messaging.channel('user-rules.js');
/******************************************************************************/
@ -102,7 +104,7 @@ function handleImportFilePicker() {
'what': 'setUserRules',
'temporaryRules': rulesFromHTML('#diff .right li') + '\n' + this.result
};
messaging.ask(request, processUserRules);
messager.send(request, processUserRules);
};
var file = this.files[0];
if ( file === undefined || file.name === '' ) {
@ -130,10 +132,9 @@ var startImportFilePicker = function() {
/******************************************************************************/
function exportUserRulesToFile() {
chrome.downloads.download({
vAPI.download({
'url': 'data:text/plain,' + encodeURIComponent(rulesFromHTML('#diff .left li')),
'filename': uDom('[data-i18n="userRulesDefaultFileName"]').text(),
'saveAs': true
'filename': uDom('[data-i18n="userRulesDefaultFileName"]').text()
});
}
@ -161,7 +162,7 @@ var revertHandler = function() {
'what': 'setUserRules',
'temporaryRules': rulesFromHTML('#diff .left li')
};
messaging.ask(request, processUserRules);
messager.send(request, processUserRules);
};
/******************************************************************************/
@ -171,7 +172,7 @@ var commitHandler = function() {
'what': 'setUserRules',
'permanentRules': rulesFromHTML('#diff .right li')
};
messaging.ask(request, processUserRules);
messager.send(request, processUserRules);
};
/******************************************************************************/
@ -191,7 +192,7 @@ var editStopHandler = function(ev) {
'what': 'setUserRules',
'temporaryRules': uDom('#diff .right textarea').val()
};
messaging.ask(request, processUserRules);
messager.send(request, processUserRules);
};
/******************************************************************************/
@ -210,7 +211,7 @@ var temporaryRulesToggler = function(ev) {
'what': 'setUserRules',
'temporaryRules': rulesFromHTML('#diff .right li')
};
messaging.ask(request, processUserRules);
messager.send(request, processUserRules);
};
/******************************************************************************/
@ -221,14 +222,14 @@ uDom.onLoad(function() {
uDom('#importFilePicker').on('change', handleImportFilePicker);
uDom('#exportButton').on('click', exportUserRulesToFile);
uDom('#revertButton').on('click', revertHandler)
uDom('#commitButton').on('click', commitHandler)
uDom('#editEnterButton').on('click', editStartHandler)
uDom('#editStopButton').on('click', editStopHandler)
uDom('#editCancelButton').on('click', editCancelHandler)
uDom('#diff > .right > ul').on('click', 'li', temporaryRulesToggler)
uDom('#revertButton').on('click', revertHandler);
uDom('#commitButton').on('click', commitHandler);
uDom('#editEnterButton').on('click', editStartHandler);
uDom('#editStopButton').on('click', editStopHandler);
uDom('#editCancelButton').on('click', editCancelHandler);
uDom('#diff > .right > ul').on('click', 'li', temporaryRulesToggler);
messaging.ask({ what: 'getUserRules' }, processUserRules);
messager.send({ what: 'getUserRules' }, processUserRules);
});
/******************************************************************************/

@ -30,69 +30,17 @@
/******************************************************************************/
var gotoURL = function(details) {
if ( details.tabId ) {
chrome.tabs.update(details.tabId, { url: details.url });
} else {
chrome.tabs.create({ url: details.url });
}
vAPI.tabs.open(details);
};
/******************************************************************************/
var gotoExtensionURL = function(url) {
var hasFragment = function(url) {
return url.indexOf('#') >= 0;
};
var removeFragment = function(url) {
var pos = url.indexOf('#');
if ( pos < 0 ) {
return url;
}
return url.slice(0, pos);
};
var tabIndex = 9999;
var targetUrl = chrome.extension.getURL(url);
var urlToFind = removeFragment(targetUrl);
var currentWindow = function(tabs) {
var updateProperties = { active: true };
var i = tabs.length;
while ( i-- ) {
if ( removeFragment(tabs[i].url) !== urlToFind ) {
continue;
}
// If current tab in dashboard is different, force the new one, if
// there is one, to be activated.
if ( tabs[i].url !== targetUrl ) {
if ( hasFragment(targetUrl) ) {
updateProperties.url = targetUrl;
}
}
// Activate found matching tab
// Commented out as per:
// https://github.com/gorhill/httpswitchboard/issues/150#issuecomment-32683726
// chrome.tabs.move(tabs[0].id, { index: index + 1 });
chrome.tabs.update(tabs[i].id, updateProperties);
return;
}
chrome.tabs.create({ 'url': targetUrl, index: tabIndex + 1 });
};
var currentTab = function(tabs) {
if ( tabs.length ) {
tabIndex = tabs[0].index;
}
chrome.tabs.query({ currentWindow: true }, currentWindow);
};
// https://github.com/gorhill/httpswitchboard/issues/150
// Logic:
// - If URL is already opened in a tab, just activate tab
// - Otherwise find the current active tab and open in a tab immediately
// to the right of the active tab
chrome.tabs.query({ active: true }, currentTab);
vAPI.tabs.open({
url: url,
index: -1,
select: true
});
};
/******************************************************************************/

@ -32,73 +32,34 @@ var noopFunc = function(){};
/******************************************************************************/
// Must read: https://code.google.com/p/chromium/issues/detail?id=410868#c8
// https://github.com/gorhill/uBlock/issues/19
// https://github.com/gorhill/uBlock/issues/207
// Since we may be called asynchronously, the tab id may not exist
// anymore, so this ensures it does still exist.
exports.setIcon = function(id, imgDict, overlayStr) {
var onIconReady = function() {
if ( chrome.runtime.lastError ) {
return;
}
chrome.browserAction.setBadgeText({ tabId: id, text: overlayStr });
if ( overlayStr !== '' ) {
chrome.browserAction.setBadgeBackgroundColor({ tabId: id, color: '#666' });
}
};
chrome.browserAction.setIcon({ tabId: id, path: imgDict }, onIconReady);
};
/******************************************************************************/
exports.injectScript = function(id, details) {
chrome.tabs.executeScript(id, details);
};
/******************************************************************************/
exports.keyvalSetOne = function(key, val, callback) {
var bin = {};
bin[key] = val;
chrome.storage.local.set(bin, callback || noopFunc);
vAPI.storage.set(bin, callback || noopFunc);
};
/******************************************************************************/
exports.keyvalGetOne = function(key, callback) {
chrome.storage.local.get(key, callback);
vAPI.storage.get(key, callback);
};
/******************************************************************************/
exports.keyvalSetMany = function(dict, callback) {
chrome.storage.local.set(dict, callback || noopFunc);
vAPI.storage.set(dict, callback || noopFunc);
};
/******************************************************************************/
exports.keyvalRemoveOne = function(key, callback) {
chrome.storage.local.remove(key, callback || noopFunc);
vAPI.storage.remove(key, callback || noopFunc);
};
/******************************************************************************/
exports.keyvalRemoveAll = function(callback) {
chrome.storage.local.clear(callback || noopFunc);
};
/******************************************************************************/
exports.restart = function() {
// https://github.com/gorhill/uMatrix/issues/40
// I don't know if that helps workaround whatever Chromium bug causes
// the browser to crash.
chrome.runtime.sendMessage({ what: 'restart' }, function() {
chrome.runtime.reload();
});
vAPI.storage.clear(callback || noopFunc);
};
/******************************************************************************/

@ -91,9 +91,10 @@
</div>
<script src="lib/punycode.min.js"></script>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/messaging-client.js"></script>
<script src="js/popup.js"></script>
</body>

@ -104,10 +104,11 @@ html.rtl #spoof-user-agent-with {
<p class="para" data-i18n="privacyProcessBehindTheSceneHelp"></p>
</div>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/dashboard-common.js"></script>
<script src="js/messaging-client.js"></script>
<script src="js/privacy.js"></script>
</body>

@ -66,10 +66,11 @@ ul > li {
</ul>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/dashboard-common.js"></script>
<script src="js/messaging-client.js"></script>
<script src="js/settings.js"></script>
</body>

@ -37,10 +37,11 @@
<input class="hidden" id="importFilePicker" type="file" accept="text/plain">
<span class="hidden" data-i18n="userRulesDefaultFileName"></span>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>
<script src="js/udom.js"></script>
<script src="js/i18n.js"></script>
<script src="js/dashboard-common.js"></script>
<script src="js/messaging-client.js"></script>
<script src="js/user-rules.js"></script>
</body>

Loading…
Cancel
Save