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

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
µBlock - a browser extension to block requests. µMatrix - a browser extension to block requests.
Copyright (C) 2014 The µBlock authors Copyright (C) 2014 The uBlock authors
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by
@ -19,7 +19,7 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global self, µBlock */ /* global self, µMatrix */
// For background page // 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() { vAPI.app.restart = function() {
chrome.runtime.reload(); chrome.runtime.reload();
}; };
@ -114,7 +142,8 @@ vAPI.tabs.registerListeners = function() {
if ( popup !== undefined ) { if ( popup !== undefined ) {
return; return;
} }
return popupCandidates[details.tabId] = new PopupCandidate(details); popup = popupCandidates[details.tabId] = new PopupCandidate(details);
return popup;
}; };
var popupCandidateTest = function(details) { var popupCandidateTest = function(details) {
@ -190,11 +219,12 @@ vAPI.tabs.registerListeners = function() {
if ( typeof this.onClosed === 'function' ) { if ( typeof this.onClosed === 'function' ) {
chrome.tabs.onRemoved.addListener(this.onClosed); chrome.tabs.onRemoved.addListener(this.onClosed);
} }
}; };
/******************************************************************************/ /******************************************************************************/
// tabId: null, // active tab
vAPI.tabs.get = function(tabId, callback) { vAPI.tabs.get = function(tabId, callback) {
var onTabReady = function(tab) { var onTabReady = function(tab) {
// https://code.google.com/p/chromium/issues/detail?id=410868#c8 // 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: // properties of the details object:
// url: 'URL', // the address that will be opened // url: 'URL', // the address that will be opened
// tabId: 1, // the tab is used if set, instead of creating a new one // 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' ) { if ( typeof tabId === 'string' ) {
tabId = parseInt(tabId, 10); tabId = parseInt(tabId, 10);
} }
if ( isNaN(tabId) ) {
return;
}
chrome.tabs.reload(tabId); 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 // Since we may be called asynchronously, the tab id may not exist
// anymore, so this ensures it does still 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); tabId = parseInt(tabId, 10);
if ( isNaN(tabId) || tabId <= 0 ) {
return;
}
var onIconReady = function() { var onIconReady = function() {
if ( vAPI.lastError() ) { if ( vAPI.lastError() ) {
return; return;
@ -397,14 +439,16 @@ vAPI.setIcon = function(tabId, iconStatus, badge) {
if ( badge !== '' ) { if ( badge !== '' ) {
chrome.browserAction.setBadgeBackgroundColor({ chrome.browserAction.setBadgeBackgroundColor({
tabId: tabId, tabId: tabId,
color: '#666' color: '#000'
}); });
} }
}; };
var iconPaths = iconStatus === 'on' ? var iconSelector = typeof iconId === 'number' ? iconId : 'off';
{ '19': 'img/browsericons/icon19.png', '38': 'img/browsericons/icon38.png' } : var iconPaths = {
{ '19': 'img/browsericons/icon19-off.png', '38': 'img/browsericons/icon38-off.png' }; '19': 'img/browsericons/icon19-' + iconSelector + '.png'/* ,
'38': 'img/browsericons/icon38-' + iconSelector + '.png' */
};
chrome.browserAction.setIcon({ tabId: tabId, path: iconPaths }, onIconReady); chrome.browserAction.setIcon({ tabId: tabId, path: iconPaths }, onIconReady);
}; };
@ -449,7 +493,7 @@ vAPI.messaging.onPortMessage = function(request, port) {
return; return;
} }
console.error(Block> messaging > unknown request: %o', request); console.error(Matrix> messaging > unknown request: %o', request);
// Unhandled: // Unhandled:
// Need to callback anyways in case caller expected an answer, or // Need to callback anyways in case caller expected an answer, or
@ -568,21 +612,21 @@ vAPI.net = {};
/******************************************************************************/ /******************************************************************************/
vAPI.net.registerListeners = function() { vAPI.net.registerListeners = function() {
var µb = µBlock; var µm = µMatrix;
var µburi = µb.URI; var µmuri = µm.URI;
var normalizeRequestDetails = function(details) { var normalizeRequestDetails = function(details) {
µburi.set(details.url); µmuri.set(details.url);
details.tabId = details.tabId.toString(); 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 // The rest of the function code is to normalize type
if ( details.type !== 'other' ) { if ( details.type !== 'other' ) {
return; return;
} }
var tail = µburi.path.slice(-6); var tail = µmuri.path.slice(-6);
var pos = tail.lastIndexOf('.'); var pos = tail.lastIndexOf('.');
// https://github.com/chrisaljoudi/uBlock/issues/862 // https://github.com/chrisaljoudi/uBlock/issues/862
@ -628,6 +672,20 @@ vAPI.net.registerListeners = function() {
this.onBeforeRequest.extra 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 onHeadersReceivedClient = this.onHeadersReceived.callback;
var onHeadersReceived = function(details) { var onHeadersReceived = function(details) {
normalizeRequestDetails(details); normalizeRequestDetails(details);
@ -641,6 +699,13 @@ vAPI.net.registerListeners = function() {
}, },
this.onHeadersReceived.extra this.onHeadersReceived.extra
); );
chrome.webRequest.onErrorOccurred.addListener(
this.onErrorOccurred.callback,
{
'urls': this.onErrorOccurred.urls || ['<all_urls>']
}
);
}; };
/******************************************************************************/ /******************************************************************************/
@ -672,62 +737,57 @@ vAPI.lastError = function() {
// the web pages before uBlock was ready. // the web pages before uBlock was ready.
vAPI.onLoadAllCompleted = function() { 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.punycodeHostname = function(hostname) {
vAPI.tabs.injectScript(tabId, { return hostname;
file: 'js/contentscript-end.js',
allFrames: true,
runAt: 'document_idle'
}, scriptDone);
}; };
var scriptStart = function(tabId) {
vAPI.tabs.injectScript(tabId, { vAPI.punycodeURL = function(url) {
file: 'js/vapi-client.js', return url;
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-- ) { vAPI.browserCache = {};
tab = tabs[i];
µb.tabContextManager.commit(tab.id, tab.url); /******************************************************************************/
µb.bindTabToPageStats(tab.id);
// https://github.com/chrisaljoudi/uBlock/issues/129 vAPI.browserCache.clearByTime = function(since) {
scriptStart(tab.id); chrome.browsingData.removeCache({ since: 0 });
}
}; };
chrome.tabs.query({ url: 'http://*/*' }, bindToTabs); vAPI.browserCache.clearByOrigin = function(/* domain */) {
chrome.tabs.query({ url: 'https://*/*' }, bindToTabs); // unsupported on Chromium
}; };
/******************************************************************************/ /******************************************************************************/
vAPI.punycodeHostname = function(hostname) { vAPI.cookies = {};
return hostname;
/******************************************************************************/
vAPI.cookies.registerListeners = function() {
if ( typeof this.onChanged === 'function' ) {
chrome.cookies.onChanged.addListener(this.onChanged);
}
}; };
vAPI.punycodeURL = function(url) { /******************************************************************************/
return url;
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.'); //console.debug('vapi-client.js already injected: skipping.');
return; return;
} }
vAPI.vapiClientInjected = true; vAPI.vapiClientInjected = true;
vAPI.sessionId = String.fromCharCode(Date.now() % 25 + 97) + vAPI.sessionId = String.fromCharCode(Date.now() % 25 + 97) +
Math.random().toString(36).slice(2); Math.random().toString(36).slice(2);
vAPI.chrome = true; 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 Copyright (C) 2014 The µBlock authors
This program is free software: you can redistribute it and/or modify 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 You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}. 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 // For background page or non-background pages

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

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

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

@ -90,6 +90,8 @@ iframe {
<iframe src=""></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/udom.js"></script>
<script src="js/i18n.js"></script> <script src="js/i18n.js"></script>
<script src="js/dashboard.js"></script> <script src="js/dashboard.js"></script>

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

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

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

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

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

@ -47,7 +47,7 @@ var onCommand = function(command) {
µMatrix.revertAllRules(); µMatrix.revertAllRules();
break; break;
case 'whitelist-all': case 'whitelist-all':
chrome.tabs.query({ active: true }, whitelistAll); vAPI.tabs.get(null, whitelistAll);
break; break;
case 'open-dashboard': case 'open-dashboard':
µMatrix.utils.gotoExtensionURL('dashboard.html'); µ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 Home: https://github.com/gorhill/uMatrix
*/ */
/* jshint multistr: true */ /* global vAPI */
/* global chrome */ /* jshint multistr: true, boss: true */
// Injected into content pages
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
// https://github.com/gorhill/httpswitchboard/issues/345 // Injected into content pages
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) { (function() {
port = chrome.runtime.connect({ name: name });
port.onMessage.addListener(onPortMessage);
// https://github.com/gorhill/uBlock/issues/193 'use strict';
port.onDisconnect.addListener(stop);
};
var stop = function() { /******************************************************************************/
listenCallback = null;
port.disconnect();
port = null;
flushCallbacks();
};
if ( typeof name === 'string' && name !== '' ) { // https://github.com/chrisaljoudi/uBlock/issues/464
start(name); if ( document instanceof HTMLDocument === false ) {
//console.debug('contentscript-end.js > not a HTLMDocument');
return false;
} }
var ask = function(msg, callback) { // This can happen
if ( port === null ) { if ( !vAPI ) {
if ( typeof callback === 'function' ) { //console.debug('contentscript-end.js > vAPI not found');
callback();
}
return; return;
} }
if ( callback === undefined ) {
tell(msg); // https://github.com/chrisaljoudi/uBlock/issues/587
// Pointless to execute without the start script having done its job.
if ( !vAPI.contentscriptStartInjected ) {
return; return;
} }
var id = requestId++;
port.postMessage({ id: id, msg: msg });
requestIdToCallbackMap[id] = callback;
};
var tell = function(msg) { // https://github.com/chrisaljoudi/uBlock/issues/456
if ( port !== null ) { // Already injected?
port.postMessage({ id: 0, msg: msg }); if ( vAPI.contentscriptEndInjected ) {
//console.debug('contentscript-end.js > content script already injected');
return;
} }
}; vAPI.contentscriptEndInjected = true;
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 { var localMessager = vAPI.messaging.channel('contentscript-end.js');
start: start,
stop: stop,
ask: ask,
tell: tell,
listen: listen
};
})('contentscript-end.js');
/******************************************************************************/ /******************************************************************************/
/******************************************************************************/ /******************************************************************************/
@ -154,12 +90,10 @@ var checkScriptBlacklistedHandler = function(response) {
} }
}; };
messaging.ask({ localMessager.send({
what: 'checkScriptBlacklisted', what: 'checkScriptBlacklisted',
url: window.location.href url: window.location.href
}, }, checkScriptBlacklistedHandler);
checkScriptBlacklistedHandler
);
/******************************************************************************/ /******************************************************************************/
@ -179,12 +113,10 @@ try {
var hasLocalStorage = window.localStorage && window.localStorage.length; var hasLocalStorage = window.localStorage && window.localStorage.length;
var hasSessionStorage = window.sessionStorage && window.sessionStorage.length; var hasSessionStorage = window.sessionStorage && window.sessionStorage.length;
if ( hasLocalStorage || hasSessionStorage ) { if ( hasLocalStorage || hasSessionStorage ) {
messaging.ask({ localMessager.send({
what: 'contentScriptHasLocalStorage', what: 'contentScriptHasLocalStorage',
url: window.location.href url: window.location.href
}, }, localStorageHandler);
localStorageHandler
);
} }
// TODO: indexedDB // TODO: indexedDB
@ -291,28 +223,12 @@ var nodeListsAddedHandler = function(nodeLists) {
nodesAddedHandler(nodeLists[i], summary); nodesAddedHandler(nodeLists[i], summary);
} }
if ( summary.mustReport ) { 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() { (function() {
var summary = { var summary = {
what: 'contentScriptSummary', 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); //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 Home: https://github.com/gorhill/uMatrix
*/ */
/* global vAPI */
/* jshint multistr: true */ /* jshint multistr: true */
/* global chrome */
// Injected into content pages // 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() { (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;
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 !== '' ) { // https://github.com/chrisaljoudi/uBlock/issues/464
start(name); if ( document instanceof HTMLDocument === false ) {
//console.debug('contentscript-start.js > not a HTLMDocument');
return false;
} }
var ask = function(msg, callback) { // This can happen
if ( port === null ) { if ( !vAPI ) {
if ( typeof callback === 'function' ) { //console.debug('contentscript-start.js > vAPI not found');
callback();
}
return; return;
} }
if ( callback === undefined ) {
tell(msg);
return;
}
var id = requestId++;
port.postMessage({ id: id, msg: msg });
requestIdToCallbackMap[id] = callback;
};
var tell = function(msg) { // https://github.com/chrisaljoudi/uBlock/issues/456
if ( port !== null ) { // Already injected?
port.postMessage({ id: 0, msg: msg }); if ( vAPI.contentscriptStartInjected ) {
//console.debug('contentscript-end.js > content script already injected');
return;
} }
}; vAPI.contentscriptStartInjected = true;
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 { var localMessager = vAPI.messaging.channel('contentscript-start.js');
start: start,
stop: stop,
ask: ask,
tell: tell,
listen: listen
};
})('contentscript-start.js');
/******************************************************************************/
/******************************************************************************/ /******************************************************************************/
// If you play with this code, mind: // 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 // The port will never be used again at this point, disconnecting allows
// to browser to flush this script from memory. // to browser to flush this script from memory.
messaging.stop(); localMessager.close();
}; };
var requestDetails = { localMessager.send({
what: 'getUserAgentReplaceStr', what: 'getUserAgentReplaceStr',
hostname: window.location.hostname hostname: window.location.hostname
}; }, injectNavigatorSpoofer);
messaging.ask(requestDetails, 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. // Listen to any change in cookieland, we will update page stats accordingly.
var onChromeCookieChanged = function(changeInfo) { vAPI.cookies.onChanged = function(changeInfo) {
if ( changeInfo.removed ) { if ( changeInfo.removed ) {
return; return;
} }
@ -524,8 +524,8 @@ var onChromeCookieChanged = function(changeInfo) {
/******************************************************************************/ /******************************************************************************/
chrome.cookies.getAll({}, addCookiesToDict); vAPI.cookies.getAll(addCookiesToDict);
chrome.cookies.onChanged.addListener(onChromeCookieChanged); vAPI.cookies.registerListeners();
µm.asyncJobs.add('cookieHunterRemove', null, processRemoveQueue, 2 * 60 * 1000, true); µm.asyncJobs.add('cookieHunterRemove', null, processRemoveQueue, 2 * 60 * 1000, true);
µm.asyncJobs.add('cookieHunterClean', null, processClean, 10 * 60 * 1000, true); µm.asyncJobs.add('cookieHunterClean', null, processClean, 10 * 60 * 1000, true);

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

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

@ -198,27 +198,11 @@
/******************************************************************************/ /******************************************************************************/
µMatrix.turnOff = function() { µMatrix.turnOff = function() {
// rhill 2013-12-07: vAPI.app.start();
// Relinquish control over javascript execution to the user.
// https://github.com/gorhill/httpswitchboard/issues/74
chrome.contentSettings.javascript.clear({});
}; };
µMatrix.turnOn = function() { µMatrix.turnOn = function() {
chrome.contentSettings.javascript.clear({}); vAPI.app.stop();
// 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'
});
}; };
/******************************************************************************/ /******************************************************************************/

@ -37,7 +37,7 @@ window.addEventListener('load', function() {
var node; var node;
while ( i-- ) { while ( i-- ) {
node = nodeList[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 // copy text of <h1> if any to document title
node = document.querySelector('h1'); node = document.querySelector('h1');
@ -49,6 +49,6 @@ window.addEventListener('load', function() {
i = nodeList.length; i = nodeList.length;
while ( i-- ) { while ( i-- ) {
node = nodeList[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 Home: https://github.com/gorhill/uMatrix
*/ */
/* global messaging, uDom */ /* global vAPI, uDom */
/******************************************************************************/ /******************************************************************************/
(function() { (function() {
'use strict';
/******************************************************************************/ /******************************************************************************/
messaging.start('info.js'); var messager = vAPI.messaging.channel('info.js');
var targetUrl = 'all'; var targetUrl = 'all';
var maxRequests = 500; var maxRequests = 500;
@ -55,7 +57,7 @@ function updateRequestData(callback) {
what: 'getRequestLogs', what: 'getRequestLogs',
pageURL: targetUrl !== 'all' ? targetUrl : null pageURL: targetUrl !== 'all' ? targetUrl : null
}; };
messaging.ask(request, onResponseReceived); messager.send(request, onResponseReceived);
} }
/******************************************************************************/ /******************************************************************************/
@ -65,7 +67,7 @@ function clearRequestData() {
what: 'clearRequestLogs', what: 'clearRequestLogs',
pageURL: targetUrl !== 'all' ? targetUrl : null pageURL: targetUrl !== 'all' ? targetUrl : null
}; };
messaging.tell(request); messager.send(request);
} }
/******************************************************************************/ /******************************************************************************/
@ -93,7 +95,7 @@ function renderNumbers(set) {
var renderLocalized = function(id, map) { var renderLocalized = function(id, map) {
var uElem = uDom('#' + id); var uElem = uDom('#' + id);
var msg = chrome.i18n.getMessage(id); var msg = vAPI.i18n(id);
for ( var k in map ) { for ( var k in map ) {
if ( map.hasOwnProperty(k) === false ) { if ( map.hasOwnProperty(k) === false ) {
continue; continue;
@ -144,7 +146,7 @@ function renderPageUrls() {
// Select whatever needs to be selected // Select whatever needs to be selected
//uDom('#selectPageUrls > option[value="'+targetUrl+'"]').prop('selected', true); //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'); uDom('a').attr('target', '_blank');
}; };
messaging.ask({ messager.send({
what: 'getStats', what: 'getStats',
pageURL: targetUrl === 'all' ? null : targetUrl pageURL: targetUrl === 'all' ? null : targetUrl
}, },
@ -285,7 +287,7 @@ var clearRequests = function() {
function changeUserSettings(name, value) { function changeUserSettings(name, value) {
cachedUserSettings[name] = value; cachedUserSettings[name] = value;
messaging.tell({ messager.send({
what: 'userSettings', what: 'userSettings',
name: name, name: name,
value: value value: value
@ -404,7 +406,7 @@ uDom.onLoad(function(){
installEventHandlers(); installEventHandlers();
}; };
messaging.ask({ what: 'getUserSettings' }, onResponseReceived); messager.send({ what: 'getUserSettings' }, onResponseReceived);
renderTransientData(true); renderTransientData(true);
updateRequests(); 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 Home: https://github.com/gorhill/uMatrix
*/ */
// So there might be memory leaks related to the direct use of sendMessage(), /* global µMatrix, vAPI */
// as per https://code.google.com/p/chromium/issues/detail?id=320723. The issue /* jshint boss: true */
// 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().
/******************************************************************************/ /******************************************************************************/
/******************************************************************************* /******************************************************************************/
// 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. callback(response);
// }
// 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.
*******************************************************************************/
/******************************************************************************/ /******************************************************************************/
µMatrix.messaging = (function() { vAPI.messaging.setup(onMessage);
/******************************************************************************/ /******************************************************************************/
})();
/******************************************************************************/
/******************************************************************************/
(function() {
// popup.js
var µm = µMatrix; var µm = µMatrix;
var runtimeIdGenerator = 1;
var nameToPortMap = {};
var nameToListenerMap = {};
var nullFunc = function(){};
/******************************************************************************/ /******************************************************************************/
var listenerNameFromPortName = function(portName) { var smartReload = function(tabs) {
var pos = portName.indexOf('/'); var i = tabs.length;
if ( pos === -1 ) { while ( i-- ) {
return ''; µ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 matrixSnapshot = function(tabId, details) {
var listener = nameToListenerMap[portName]; var µmuser = µm.userSettings;
if ( listener && listener !== callback ) { var r = {
throw 'Only one listener allowed'; 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;
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 tell = function(target, msg) { var switchNames = µm.Matrix.getSwitchNames();
target += '/'; for ( var switchName in switchNames ) {
for ( var portName in nameToPortMap ) { if ( switchNames.hasOwnProperty(switchName) === false ) {
if ( nameToPortMap.hasOwnProperty(portName) === false ) {
continue; continue;
} }
if ( portName.indexOf(target) === 0 ) { r.tSwitches[switchName] = µm.tMatrix.evaluateSwitchZ(switchName, r.scope);
nameToPortMap[portName].postMessage({ id: -1, msg: msg }); 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) { var matrixSnapshotFromTabId = function(details, callback) {
// Background page handler if ( details.targetTabId ) {
defaultHandler(msg, null, nullFunc); callback(matrixSnapshot(details.targetTabId, details));
return;
}
vAPI.tabs.get(null, function(tab) {
callback(matrixSnapshot(tab.id, details));
});
};
// Extension pages & content scripts handlers /******************************************************************************/
for ( var portName in nameToPortMap ) {
if ( nameToPortMap.hasOwnProperty(portName) === false ) { var onMessage = function(request, sender, callback) {
continue; // 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);
} }
nameToPortMap[portName].postMessage({ id: -1, msg: msg }); 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 vAPI.messaging.UNHANDLED;
} }
callback(response);
}; };
vAPI.messaging.listen('popup.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/ /******************************************************************************/
var onMessage = function(request, port) { // content scripts
var reqId = request.id;
// Annoucement: dispatch everywhere. (function() {
if ( reqId < 0 ) {
announce(request.msg); 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; return;
} }
var listener = listenerFromPortName(port.name) || defaultHandler; var pageURL = µm.pageUrlFromTabId(tabId);
// Being told var pageStats = µm.pageStatsFromPageUrl(pageURL);
if ( reqId === 0 ) { var µmuri = µm.URI.set(details.locationURL);
listener(request.msg, port.sender, nullFunc); var frameURL = µmuri.normalizedURI();
return; 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 if ( url === '{inline_script}' ) {
listener(request.msg, port.sender, function(response) { url = frameURL + '{inline_script}';
port.postMessage({ }
id: reqId, r = µm.filterRequest(pageURL, 'script', url);
msg: response !== undefined ? response : null 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 // Async
switch ( request.what ) { switch ( request.what ) {
case 'getAssetContent': default:
return µm.assets.getLocal(request.url, callback); 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: default:
break; break;
} }
@ -149,89 +511,385 @@ function defaultHandler(request, sender, callback) {
var response; var response;
switch ( request.what ) { switch ( request.what ) {
case 'forceReloadTab': default:
µm.forceReload(request.tabId); 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; break;
}
case 'getUserSettings': // Sync
response = µm.userSettings; 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; break;
case 'gotoExtensionURL': case 'setMatrixSwitch':
µm.utils.gotoExtensionURL(request.url); µm.tMatrix.setSwitch(request.switchName, '*', request.state);
if ( µm.pMatrix.setSwitch(request.switchName, '*', request.state) ) {
µm.saveMatrix();
}
break; break;
case 'gotoURL': default:
µm.utils.gotoURL(request); 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; break;
}
case 'reloadHostsFiles': // Sync
µm.reloadHostsFiles(request.switches, request.update); var response;
switch ( request.what ) {
case 'getUserRules':
response = {
temporaryRules: µm.tMatrix.toString(),
permanentRules: µm.pMatrix.toString()
};
break; break;
case 'userSettings': case 'setUserRules':
response = µm.changeUserSettings(request.name, request.value); 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; break;
default: 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; break;
} }
// Sync
var response;
switch ( request.what ) {
case 'purgeCache':
µm.assets.purge(request.path);
break;
default:
return vAPI.messaging.UNHANDLED;
}
callback(response); 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;
};
/******************************************************************************/ /******************************************************************************/
// Port disconnected, relay this information to apropriate listener. 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;
var onDisconnect = function(port) { case 'getStats':
// Notify listener of the disconnection -- using a reserved message id. var pageStore = µm.pageStats[request.pageURL];
var listener = listenerFromPortName(port.name) || defaultHandler; response = {
var msg = { globalNetStats: µm.requestStats,
'what': 'disconnected', pageNetStats: pageStore ? pageStore.requestStats : null,
'which': listenerNameFromPortName(port.name) 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. case 'clearRequestLogs':
if ( nameToPortMap.hasOwnProperty(port.name) ) { clearRequestLog(request.pageURL);
delete nameToPortMap[port.name]; break;
port.onMessage.removeListener(onMessage);
port.onDisconnect.removeListener(onDisconnect); default:
return vAPI.messaging.UNHANDLED;
} }
callback(response);
}; };
vAPI.messaging.listen('info.js', onMessage);
})();
/******************************************************************************/
/******************************************************************************/ /******************************************************************************/
var onConnect = function(port) { // about.js
// We must have a port name.
if ( typeof port.name !== 'string' || port.name === '' ) { (function() {
console.error('messaging.js / onConnectHandler(): no port name!');
return; 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 var onAllRemoved = function() {
port.name += '/' + runtimeIdGenerator++; // 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; // If we are going to restore all, might as well wipe out clean local
port.onMessage.addListener(onMessage); // storage
port.onDisconnect.addListener(onDisconnect); µm.XAL.keyvalRemoveAll(onAllRemoved);
}; };
/******************************************************************************/ /******************************************************************************/
chrome.runtime.onConnect.addListener(onConnect); var resetUserData = function() {
var onAllRemoved = function() {
vAPI.app.restart();
};
µm.XAL.keyvalRemoveAll(onAllRemoved);
};
/******************************************************************************/ /******************************************************************************/
return { var onMessage = function(request, sender, callback) {
listen: listen, // Async
tell: tell, switch ( request.what ) {
announce: announce, default:
defaultHandler: defaultHandler 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() { µMatrix.PageStore = (function() {
'use strict';
/******************************************************************************/ /******************************************************************************/
var µm = µMatrix; 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. // notifying me, and this causes internal cached state to be out of sync.
PageStore.prototype.updateBadge = function(tabId) { PageStore.prototype.updateBadge = function(tabId) {
// Icon var iconId = null;
var iconPath;
var badgeStr = ''; var badgeStr = '';
var total = this.perLoadAllowedRequestCount + this.perLoadBlockedRequestCount; var total = this.perLoadAllowedRequestCount + this.perLoadBlockedRequestCount;
if ( total ) { if ( total ) {
var squareSize = 19; var squareSize = 19;
var greenSize = squareSize * Math.sqrt(this.perLoadAllowedRequestCount / total); var greenSize = squareSize * Math.sqrt(this.perLoadAllowedRequestCount / total);
greenSize = greenSize < squareSize/2 ? Math.ceil(greenSize) : Math.floor(greenSize); iconId = greenSize < squareSize/2 ? Math.ceil(greenSize) : Math.floor(greenSize);
iconPath = 'img/browsericons/icon19-' + greenSize + '.png';
badgeStr = µm.formatCount(this.distinctRequestCount); 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 factory: pageStoreFactory
}; };
/******************************************************************************/
})(); })();
/******************************************************************************/ /******************************************************************************/

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

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

@ -19,16 +19,18 @@
Home: https://github.com/gorhill/uMatrix Home: https://github.com/gorhill/uMatrix
*/ */
/* global messaging, uDom */ /* global vAPI, uDom */
/* jshint multistr: true */ /* jshint multistr: true */
/******************************************************************************/ /******************************************************************************/
(function() { (function() {
'use strict';
/******************************************************************************/ /******************************************************************************/
messaging.start('settings.js'); var messager = vAPI.messaging.channel('settings.js');
var cachedUserSettings = {}; var cachedUserSettings = {};
@ -68,7 +70,7 @@ var onSubframeColorChanged = function() {
/******************************************************************************/ /******************************************************************************/
function changeUserSettings(name, value) { function changeUserSettings(name, value) {
messaging.tell({ messager.send({
what: 'userSettings', what: 'userSettings',
name: name, name: name,
value: value value: value
@ -126,7 +128,7 @@ uDom.onLoad(function() {
installEventHandlers(); 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 // Browser data jobs
(function() { (function() {
@ -136,8 +57,8 @@ chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigateCallback);
} }
µm.clearBrowserCacheCycle = µm.userSettings.clearBrowserCacheAfter; µm.clearBrowserCacheCycle = µm.userSettings.clearBrowserCacheAfter;
µm.browserCacheClearedCounter++; µm.browserCacheClearedCounter++;
chrome.browsingData.removeCache({ since: 0 }); vAPI.browserCache.clearByTime(0);
// console.debug('clearBrowserCacheCallback()> chrome.browsingData.removeCache() called'); // console.debug('clearBrowserCacheCallback()> vAPI.browserCache.clearByTime() called');
}; };
µMatrix.asyncJobs.add('clearBrowserCache', null, jobCallback, 15 * 60 * 1000, true); µMatrix.asyncJobs.add('clearBrowserCache', null, jobCallback, 15 * 60 * 1000, true);
@ -177,13 +98,14 @@ chrome.webNavigation.onBeforeNavigate.addListener(onBeforeNavigateCallback);
var i = tabs.length; var i = tabs.length;
// console.debug('start.js > binding %d tabs', i); // console.debug('start.js > binding %d tabs', i);
while ( i-- ) { while ( i-- ) {
µm.tabContextManager.commit(tabs[i].id, tabs[i].url);
µm.bindTabToPageStats(tabs[i].id, tabs[i].url); µm.bindTabToPageStats(tabs[i].id, tabs[i].url);
} }
µm.webRequest.start(); µm.webRequest.start();
}; };
var queryTabs = function() { var queryTabs = function() {
chrome.tabs.query({ url: '<all_urls>' }, bindTabs); vAPI.tabs.getAll(bindTabs);
}; };
µm.load(queryTabs); µm.load(queryTabs);

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

@ -21,11 +21,407 @@
/* global chrome, µMatrix */ /* 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) // 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 // https://github.com/gorhill/httpswitchboard/issues/303
// At this point, the URL has been page-URL-normalized // At this point, the URL has been page-URL-normalized
@ -57,46 +453,31 @@
/******************************************************************************/ /******************************************************************************/
// https://github.com/gorhill/httpswitchboard/issues/303 // Create an entry for the tab if it doesn't exist
// 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.
µMatrix.normalizePageURL = function(pageURL) { µm.bindTabToPageStats = function(tabId, context) {
var uri = this.URI.set(pageURL); if ( vAPI.isBehindTheSceneTabId(tabId) === false ) {
if ( uri.scheme === 'https' || uri.scheme === 'http' ) { this.updateBadgeAsync(tabId);
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/';
} }
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 // https://github.com/gorhill/httpswitchboard/issues/303
// Don't rebind pages blocked by µMatrix. // Don't rebind pages blocked by µMatrix.
var blockedRootFramePrefix = this.webRequest.blockedRootFramePrefix; var blockedRootFramePrefix = this.webRequest.blockedRootFramePrefix;
if ( pageURL.slice(0, blockedRootFramePrefix.length) === blockedRootFramePrefix ) { if ( rawURL.lastIndexOf(blockedRootFramePrefix, 0) === 0 ) {
return null; return null;
} }
var pageStore; var pageStore;
var pageURL = tabContext.normalURL;
// https://github.com/gorhill/httpswitchboard/issues/303
// Normalize to a page-URL.
pageURL = this.normalizePageURL(pageURL);
// The previous page URL, if any, associated with the tab // The previous page URL, if any, associated with the tab
if ( this.tabIdToPageUrl.hasOwnProperty(tabId) ) { if ( this.tabIdToPageUrl.hasOwnProperty(tabId) ) {
@ -159,7 +540,7 @@
/******************************************************************************/ /******************************************************************************/
µMatrix.unbindTabFromPageStats = function(tabId) { µm.unbindTabFromPageStats = function(tabId) {
if ( this.tabIdToPageUrl.hasOwnProperty(tabId) === false ) { if ( this.tabIdToPageUrl.hasOwnProperty(tabId) === false ) {
return; return;
} }
@ -179,7 +560,7 @@
// Log a request // Log a request
µMatrix.recordFromTabId = function(tabId, type, url, blocked) { µm.recordFromTabId = function(tabId, type, url, blocked) {
var pageStats = this.pageStatsFromTabId(tabId); var pageStats = this.pageStatsFromTabId(tabId);
if ( pageStats ) { if ( pageStats ) {
pageStats.recordRequest(type, url, blocked); 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); var pageStats = this.pageStatsFromPageUrl(pageUrl);
if ( pageStats ) { if ( pageStats ) {
pageStats.recordRequest(type, url, blocked, reason); pageStats.recordRequest(type, url, blocked, reason);
@ -196,7 +577,7 @@
/******************************************************************************/ /******************************************************************************/
µMatrix.onPageLoadCompleted = function(pageURL) { µm.onPageLoadCompleted = function(pageURL) {
var pageStats = this.pageStatsFromPageUrl(pageURL); var pageStats = this.pageStatsFromPageUrl(pageURL);
if ( !pageStats ) { if ( !pageStats ) {
return; 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' ) { if ( which === 'none' ) {
return; return;
} }
@ -224,7 +605,6 @@
// which === 'all' // which === 'all'
var reloadTabs = function(chromeTabs) { var reloadTabs = function(chromeTabs) {
var µm = µMatrix;
var tabId; var tabId;
var i = chromeTabs.length; var i = chromeTabs.length;
while ( i-- ) { while ( i-- ) {
@ -236,7 +616,7 @@
}; };
var getTabs = function() { var getTabs = function() {
chrome.tabs.query({ status: 'complete' }, reloadTabs); vAPI.tabs.getAll(reloadTabs);
}; };
this.asyncJobs.add('smartReloadTabs', null, getTabs, 500); this.asyncJobs.add('smartReloadTabs', null, getTabs, 500);
@ -246,7 +626,7 @@
// Reload content of a tab // Reload content of a tab
µMatrix.smartReloadTab = function(tabId) { µm.smartReloadTab = function(tabId) {
var pageStats = this.pageStatsFromTabId(tabId); var pageStats = this.pageStatsFromTabId(tabId);
if ( !pageStats ) { if ( !pageStats ) {
//console.error('HTTP Switchboard> µMatrix.smartReloadTab(): page stats for tab id %d not found', tabId); //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); // console.log('old state: %o\nnew state: %o', oldState, newState);
if ( mustReload ) { if ( mustReload ) {
chrome.tabs.reload(tabId); vAPI.tabs.reload(tabId);
} }
// pageStats.state = newState; // pageStats.state = newState;
}; };
@ -317,13 +697,13 @@
// `chrome-devtools://devtools/devtools.html` // `chrome-devtools://devtools/devtools.html`
// etc. // etc.
µMatrix.tabExists = function(tabId) { µm.tabExists = function(tabId) {
return !!this.pageUrlFromTabId(tabId); return !!this.pageUrlFromTabId(tabId);
}; };
/******************************************************************************/ /******************************************************************************/
µMatrix.computeTabState = function(tabId) { µm.computeTabState = function(tabId) {
var pageStats = this.pageStatsFromTabId(tabId); var pageStats = this.pageStatsFromTabId(tabId);
if ( !pageStats ) { if ( !pageStats ) {
//console.error('tab.js > µMatrix.computeTabState(): page stats for tab id %d not found', tabId); //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]; return this.tabIdToPageUrl[tabId];
}; };
µMatrix.pageUrlFromPageStats = function(pageStats) { µm.pageUrlFromPageStats = function(pageStats) {
if ( pageStats ) { if ( pageStats ) {
return pageStats.pageUrl; return pageStats.pageUrl;
} }
return undefined; return undefined;
}; };
µMatrix.pageStatsFromTabId = function(tabId) { µm.pageStatsFromTabId = function(tabId) {
var pageUrl = this.tabIdToPageUrl[tabId]; var pageUrl = this.tabIdToPageUrl[tabId];
if ( pageUrl ) { if ( pageUrl ) {
return this.pageStats[pageUrl]; return this.pageStats[pageUrl];
@ -377,7 +757,7 @@
return undefined; return undefined;
}; };
µMatrix.pageStatsFromPageUrl = function(pageURL) { µm.pageStatsFromPageUrl = function(pageURL) {
if ( pageURL ) { if ( pageURL ) {
return this.pageStats[this.normalizePageURL(pageURL)]; return this.pageStats[this.normalizePageURL(pageURL)];
} }
@ -386,7 +766,7 @@
/******************************************************************************/ /******************************************************************************/
µMatrix.resizeLogBuffers = function(size) { µm.resizeLogBuffers = function(size) {
var pageStores = this.pageStats; var pageStores = this.pageStats;
for ( var pageURL in pageStores ) { for ( var pageURL in pageStores ) {
if ( pageStores.hasOwnProperty(pageURL) ) { if ( pageStores.hasOwnProperty(pageURL) ) {
@ -397,15 +777,14 @@
/******************************************************************************/ /******************************************************************************/
µMatrix.forceReload = function(tabId) { µm.forceReload = function(tabId) {
chrome.tabs.reload(tabId, { bypassCache: true }); vAPI.tabs.reload(tabId, { bypassCache: true });
}; };
/******************************************************************************/ /******************************************************************************/
// Garbage collect stale url stats entries // Garbage collect stale url stats entries
(function() { (function() {
var µm = µMatrix;
var gcPageStats = function() { var gcPageStats = function() {
var pageStore; var pageStore;
var now = Date.now(); var now = Date.now();
@ -447,7 +826,7 @@
// Time somewhat arbitrary: If a web page has not been in a tab // Time somewhat arbitrary: If a web page has not been in a tab
// for some time minutes, flush its stats. // for some time minutes, flush its stats.
µMatrix.asyncJobs.add( µm.asyncJobs.add(
'gcPageStats', 'gcPageStats',
null, null,
gcPageStats, gcPageStats,
@ -455,3 +834,7 @@
true true
); );
})(); })();
/******************************************************************************/
})();

@ -197,15 +197,15 @@ var onBeforeChromeExtensionRequestHandler = function(details) {
var onBeforeRootFrameRequestHandler = function(details) { var onBeforeRootFrameRequestHandler = function(details) {
var µm = µMatrix; var µm = µMatrix;
var requestURL = details.url;
// Do not ignore traffic outside tabs
var tabId = details.tabId; var tabId = details.tabId;
if ( tabId < 0 ) {
µm.tabContextManager.push(tabId, requestURL);
if ( vAPI.isBehindTheSceneTabId(tabId) ) {
tabId = µm.behindTheSceneTabId; tabId = µm.behindTheSceneTabId;
} } else {
// It's a root frame, bind to a new page store µm.bindTabToPageStats(tabId);
else {
µm.bindTabToPageStats(tabId, details.url);
} }
var uri = µm.URI.set(details.url); var uri = µm.URI.set(details.url);
@ -213,7 +213,6 @@ var onBeforeRootFrameRequestHandler = function(details) {
return; return;
} }
var requestURL = uri.normalizedURI();
var requestHostname = uri.hostname; var requestHostname = uri.hostname;
var pageStore = µm.pageStatsFromTabId(tabId); 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) { var onErrorOccurredHandler = function(details) {
// console.debug('onErrorOccurred()> "%s": %o', details.url, details); // console.debug('onErrorOccurred()> "%s": %o', details.url, details);
var requestType = requestTypeNormalizer[details.type]; var requestType = requestTypeNormalizer[details.type];
@ -798,68 +793,54 @@ var requestTypeNormalizer = {
/******************************************************************************/ /******************************************************************************/
var start = function() { vAPI.net.onBeforeRequest = {
chrome.webRequest.onBeforeRequest.addListener( urls: [
//function(details) {
// quickProfiler.start('onBeforeRequest');
// var r = onBeforeRequestHandler(details);
// quickProfiler.stop();
// return r;
//},
onBeforeRequestHandler,
{
"urls": [
"http://*/*", "http://*/*",
"https://*/*", "https://*/*",
"chrome-extension://*/*" "chrome-extension://*/*"
], ],
"types": [ extra: [ 'blocking' ],
"main_frame", callback: onBeforeRequestHandler
"sub_frame", };
'stylesheet',
"script",
"image",
"object",
"xmlhttprequest",
"other"
]
},
[ "blocking" ]
);
//console.log('µMatrix > Beginning to intercept net requests at %s', (new Date()).toISOString());
chrome.webRequest.onBeforeSendHeaders.addListener( vAPI.net.onBeforeSendHeaders = {
onBeforeSendHeadersHandler, urls: [
{
'urls': [
"http://*/*", "http://*/*",
"https://*/*" "https://*/*"
] ],
}, types: [
['blocking', 'requestHeaders'] "main_frame",
); "sub_frame"
],
extra: [ 'blocking', 'requestHeaders' ],
callback: onBeforeSendHeadersHandler
};
chrome.webRequest.onHeadersReceived.addListener( vAPI.net.onHeadersReceived = {
onHeadersReceived, urls: [
{
'urls': [
"http://*/*", "http://*/*",
"https://*/*" "https://*/*"
] ],
}, types: [
['blocking', 'responseHeaders'] "main_frame",
); "sub_frame"
],
extra: [ 'blocking', 'responseHeaders' ],
callback: onHeadersReceived
};
chrome.webRequest.onErrorOccurred.addListener( vAPI.net.onErrorOccurred = {
onErrorOccurredHandler, urls: [
{
'urls': [
"http://*/*", "http://*/*",
"https://*/*" "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 Copyright (C) 2014 Raymond Hill
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
@ -19,7 +19,9 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/******************************************************************************/ /* global DOMTokenList */
/* exported uDom */
/******************************************************************************/ /******************************************************************************/
// It's just a silly, minimalist DOM framework: this allows me to not rely // It's just a silly, minimalist DOM framework: this allows me to not rely
@ -30,6 +32,8 @@
var uDom = (function() { var uDom = (function() {
'use strict';
/******************************************************************************/ /******************************************************************************/
var DOMList = function() { var DOMList = function() {
@ -136,7 +140,7 @@ var addHTMLToList = function(list, html) {
var cTag = matches[1]; var cTag = matches[1];
var pTag = pTagOfChildTag[cTag] || 'div'; var pTag = pTagOfChildTag[cTag] || 'div';
var p = document.createElement(pTag); var p = document.createElement(pTag);
p.innerHTML = html; vAPI.insertHTML(p, html);
// Find real parent // Find real parent
var c = p.querySelector(cTag); var c = p.querySelector(cTag);
p = c.parentNode; 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) { DOMList.prototype.forEach = function(fn) {
var n = this.nodes.length; var n = this.nodes.length;
for ( var i = 0; i < n; i++ ) { 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) { DOMList.prototype.attr = function(attr, value) {
var i = this.nodes.length; var i = this.nodes.length;
if ( value === undefined && typeof attr !== 'object' ) { if ( value === undefined && typeof attr !== 'object' ) {
@ -543,7 +575,7 @@ DOMList.prototype.html = function(html) {
return i ? this.nodes[0].innerHTML : ''; return i ? this.nodes[0].innerHTML : '';
} }
while ( i-- ) { while ( i-- ) {
this.nodes[i].innerHTML = html; vAPI.insertHTML(this.nodes[i], html);
} }
return this; 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) { var makeEventHandler = function(selector, callback) {
return function(event) { return function(event) {
var dispatcher = event.currentTarget; var dispatcher = event.currentTarget;
@ -660,7 +710,7 @@ DOMList.prototype.on = function(etype, selector, callback) {
var i = this.nodes.length; var i = this.nodes.length;
while ( i-- ) { while ( i-- ) {
this.nodes[i].addEventListener(etype, callback, selector !== undefined); listenerEntries.push(new ListenerEntry(this.nodes[i], etype, selector !== undefined, callback));
} }
return this; 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; 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() { µMatrix.URI = (function() {
'use strict';
/******************************************************************************/ /******************************************************************************/
// Favorite regex tool: http://regex101.com/ // Favorite regex tool: http://regex101.com/
@ -49,6 +49,7 @@ var reRFC3986 = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?/;
// Derived // Derived
var reSchemeFromURI = /^[^:\/?#]+:/; var reSchemeFromURI = /^[^:\/?#]+:/;
var reAuthorityFromURI = /^(?:[^:\/?#]+:)?(\/\/[^\/?#]+)/; 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 // 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 // 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. // The most used function, so it better be fast.
URI.hostnameFromURI = function(uri) { 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 ) { if ( !matches ) {
return ''; 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 // It is expected that there is higher-scoped `publicSuffixList` lingering
// somewhere. Cache it. See <https://github.com/gorhill/publicsuffixlist.js>. // somewhere. Cache it. See <https://github.com/gorhill/publicsuffixlist.js>.
var psl = publicSuffixList; 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() { var domainCacheEntryFactory = function(domain) {
return this.domainFromHostname(this.hostname); 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) { URI.domainFromURI = function(uri) {

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

@ -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) { exports.keyvalSetOne = function(key, val, callback) {
var bin = {}; var bin = {};
bin[key] = val; bin[key] = val;
chrome.storage.local.set(bin, callback || noopFunc); vAPI.storage.set(bin, callback || noopFunc);
}; };
/******************************************************************************/ /******************************************************************************/
exports.keyvalGetOne = function(key, callback) { exports.keyvalGetOne = function(key, callback) {
chrome.storage.local.get(key, callback); vAPI.storage.get(key, callback);
}; };
/******************************************************************************/ /******************************************************************************/
exports.keyvalSetMany = function(dict, callback) { exports.keyvalSetMany = function(dict, callback) {
chrome.storage.local.set(dict, callback || noopFunc); vAPI.storage.set(dict, callback || noopFunc);
}; };
/******************************************************************************/ /******************************************************************************/
exports.keyvalRemoveOne = function(key, callback) { exports.keyvalRemoveOne = function(key, callback) {
chrome.storage.local.remove(key, callback || noopFunc); vAPI.storage.remove(key, callback || noopFunc);
}; };
/******************************************************************************/ /******************************************************************************/
exports.keyvalRemoveAll = function(callback) { exports.keyvalRemoveAll = function(callback) {
chrome.storage.local.clear(callback || noopFunc); vAPI.storage.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();
});
}; };
/******************************************************************************/ /******************************************************************************/

@ -91,9 +91,10 @@
</div> </div>
<script src="lib/punycode.min.js"></script> <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/udom.js"></script>
<script src="js/i18n.js"></script> <script src="js/i18n.js"></script>
<script src="js/messaging-client.js"></script>
<script src="js/popup.js"></script> <script src="js/popup.js"></script>
</body> </body>

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

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

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

Loading…
Cancel
Save