This commit is contained in:
gorhill 2015-09-30 09:33:38 -04:00
parent cc17a77b0a
commit 8d294869fe
7 changed files with 181 additions and 60 deletions

View file

@ -30,6 +30,7 @@ const {Services} = Cu.import('resource://gre/modules/Services.jsm', null);
const {XPCOMUtils} = Cu.import('resource://gre/modules/XPCOMUtils.jsm', null); const {XPCOMUtils} = Cu.import('resource://gre/modules/XPCOMUtils.jsm', null);
const hostName = Services.io.newURI(Components.stack.filename, null, null).host; const hostName = Services.io.newURI(Components.stack.filename, null, null).host;
const rpcEmitterName = hostName + ':child-process-message';
//Cu.import('resource://gre/modules/devtools/Console.jsm'); //Cu.import('resource://gre/modules/devtools/Console.jsm');
@ -239,9 +240,25 @@ const contentObserver = {
wantXHRConstructor: false wantXHRConstructor: false
}); });
if ( Services.cpmm ) {
sandbox.rpc = function(details) {
var svc = Services;
if ( svc === undefined ) { return; }
var cpmm = svc.cpmm;
if ( !cpmm ) { return; }
var r = cpmm.sendSyncMessage(rpcEmitterName, details);
if ( Array.isArray(r) ) {
return r[0];
}
};
} else {
sandbox.rpc = function() {};
}
sandbox.injectScript = function(script) { sandbox.injectScript = function(script) {
if ( Services !== undefined ) { var svc = Services;
Services.scriptloader.loadSubScript(script, sandbox); if ( svc !== undefined ) {
svc.scriptloader.loadSubScript(script, sandbox);
} else { } else {
// Sandbox appears void. // Sandbox appears void.
// I've seen this happens, need to investigate why. // I've seen this happens, need to investigate why.
@ -258,9 +275,10 @@ const contentObserver = {
sandbox.removeMessageListener(); sandbox.removeMessageListener();
sandbox.addMessageListener = sandbox.addMessageListener =
sandbox.injectScript = sandbox.injectScript =
sandbox.outerShutdown =
sandbox.removeMessageListener = sandbox.removeMessageListener =
sandbox.sendAsyncMessage = sandbox.rpc =
sandbox.outerShutdown = function(){}; sandbox.sendAsyncMessage = function(){};
sandbox.vAPI = {}; sandbox.vAPI = {};
messager = null; messager = null;
}; };
@ -412,13 +430,19 @@ const LocationChangeListener = function(docShell) {
var requestor = docShell.QueryInterface(Ci.nsIInterfaceRequestor); var requestor = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
var ds = requestor.getInterface(Ci.nsIWebProgress); var ds = requestor.getInterface(Ci.nsIWebProgress);
var mm = requestor.getInterface(Ci.nsIContentFrameMessageManager); if ( !ds ) {
return;
if ( ds && mm && typeof mm.sendAsyncMessage === 'function' ) {
this.docShell = ds;
this.messageManager = mm;
ds.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
} }
var mm = requestor.getInterface(Ci.nsIContentFrameMessageManager);
if ( !mm ) {
return;
}
if ( typeof mm.sendAsyncMessage !== 'function' ) {
return;
}
this.docShell = ds;
this.messageManager = mm;
ds.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
}; };
LocationChangeListener.prototype.QueryInterface = XPCOMUtils.generateQI([ LocationChangeListener.prototype.QueryInterface = XPCOMUtils.generateQI([

View file

@ -19,13 +19,11 @@
Home: https://github.com/gorhill/uBlock Home: https://github.com/gorhill/uBlock
*/ */
/* global addMessageListener, removeMessageListener, docShell */
/******************************************************************************/ /******************************************************************************/
var locationChangeListener; // Keep alive while frameScript is alive // https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Frame_script_environment
(function() { (function(context) {
'use strict'; 'use strict';
@ -52,25 +50,36 @@ let injectContentScripts = function(win) {
}; };
let onLoadCompleted = function() { let onLoadCompleted = function() {
removeMessageListener('ublock0-load-completed', onLoadCompleted); context.removeMessageListener('ublock0-load-completed', onLoadCompleted);
injectContentScripts(content); injectContentScripts(context.content);
}; };
context.addMessageListener('ublock0-load-completed', onLoadCompleted);
addMessageListener('ublock0-load-completed', onLoadCompleted); let shutdown = function(ev) {
if ( ev.target !== context ) {
return;
}
context.removeMessageListener('ublock0-load-completed', onLoadCompleted);
context.removeEventListener('unload', shutdown);
context.locationChangeListener = null;
LocationChangeListener = null;
contentObserver = null;
};
context.addEventListener('unload', shutdown);
if ( docShell ) { if ( context.docShell ) {
let Ci = Components.interfaces; let Ci = Components.interfaces;
let wp = docShell.QueryInterface(Ci.nsIInterfaceRequestor) let wp = context.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress); .getInterface(Ci.nsIWebProgress);
let dw = wp.DOMWindow; let dw = wp.DOMWindow;
if ( dw === dw.top ) { if ( dw === dw.top ) {
locationChangeListener = new LocationChangeListener(docShell); context.locationChangeListener = new LocationChangeListener(context.docShell);
} }
} }
/******************************************************************************/ /******************************************************************************/
})(); })(this);
/******************************************************************************/ /******************************************************************************/

View file

@ -70,7 +70,7 @@ vAPI.localStorage.setDefaultBool('forceLegacyToolbarButton', false);
var cleanupTasks = []; var cleanupTasks = [];
// This must be updated manually, every time a new task is added/removed // This must be updated manually, every time a new task is added/removed
var expectedNumberOfCleanups = 7; var expectedNumberOfCleanups = 8;
window.addEventListener('unload', function() { window.addEventListener('unload', function() {
if ( typeof vAPI.app.onShutdown === 'function' ) { if ( typeof vAPI.app.onShutdown === 'function' ) {
@ -90,10 +90,11 @@ window.addEventListener('unload', function() {
} }
// frameModule needs to be cleared too // frameModule needs to be cleared too
var frameModuleURL = vAPI.getURL('frameModule.js');
var frameModule = {}; var frameModule = {};
Cu.import(vAPI.getURL('frameModule.js'), frameModule); Cu.import(frameModuleURL, frameModule);
frameModule.contentObserver.unregister(); frameModule.contentObserver.unregister();
Cu.unload(vAPI.getURL('frameModule.js')); Cu.unload(frameModuleURL);
}); });
/******************************************************************************/ /******************************************************************************/
@ -987,15 +988,15 @@ var tabWatcher = (function() {
}; };
// https://developer.mozilla.org/en-US/docs/Web/Events/TabOpen // https://developer.mozilla.org/en-US/docs/Web/Events/TabOpen
var onOpen = function({target}) { //var onOpen = function({target}) {
var tabId = tabIdFromTarget(target); // var tabId = tabIdFromTarget(target);
var browser = browserFromTabId(tabId); // var browser = browserFromTabId(tabId);
vAPI.tabs.onNavigation({ // vAPI.tabs.onNavigation({
frameId: 0, // frameId: 0,
tabId: tabId, // tabId: tabId,
url: browser.currentURI.asciiSpec, // url: browser.currentURI.asciiSpec,
}); // });
}; //};
// https://developer.mozilla.org/en-US/docs/Web/Events/TabShow // https://developer.mozilla.org/en-US/docs/Web/Events/TabShow
var onShow = function({target}) { var onShow = function({target}) {
@ -1208,7 +1209,7 @@ vAPI.messaging = {
return Cc['@mozilla.org/globalmessagemanager;1'] return Cc['@mozilla.org/globalmessagemanager;1']
.getService(Ci.nsIMessageListenerManager); .getService(Ci.nsIMessageListenerManager);
}, },
frameScript: vAPI.getURL('frameScript.js'), frameScriptURL: vAPI.getURL('frameScript.js'),
listeners: {}, listeners: {},
defaultHandler: null, defaultHandler: null,
NOOPFUNC: function(){}, NOOPFUNC: function(){},
@ -1438,7 +1439,7 @@ vAPI.messaging.setup = function(defaultHandler) {
this.onMessage this.onMessage
); );
this.globalMessageManager.loadFrameScript(this.frameScript, true); this.globalMessageManager.loadFrameScript(this.frameScriptURL, true);
cleanupTasks.push(function() { cleanupTasks.push(function() {
var gmm = vAPI.messaging.globalMessageManager; var gmm = vAPI.messaging.globalMessageManager;
@ -1452,7 +1453,7 @@ vAPI.messaging.setup = function(defaultHandler) {
}) })
); );
gmm.removeDelayedFrameScript(vAPI.messaging.frameScript); gmm.removeDelayedFrameScript(vAPI.messaging.frameScriptURL);
gmm.removeMessageListener( gmm.removeMessageListener(
location.host + ':background', location.host + ':background',
vAPI.messaging.onMessage vAPI.messaging.onMessage
@ -1471,6 +1472,60 @@ vAPI.messaging.broadcast = function(message) {
); );
}; };
/******************************************************************************/
/******************************************************************************/
// Synchronous messaging: Firefox allows this. Chromium does not allow this.
// Sometimes there is no way around synchronous messaging, as long as:
// - the code at the other end execute fast and return quickly.
// - it's not abused.
// Original rationale is <https://github.com/gorhill/uBlock/issues/756>.
// Synchronous messaging is a good solution for this case because:
// - It's done only *once* per page load. (Keep in mind there is already a
// sync message sent for each single network request on a page and it's not
// an issue, because the code executed is trivial, which is the key -- see
// shouldLoadListener below).
// - The code at the other end is fast.
// Though vAPI.rpcReceiver was brought forth because of this one case, I
// generalized the concept for whatever future need for synchronous messaging
// which might arise.
// https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Message_Manager/Message_manager_overview#Content_frame_message_manager
vAPI.rpcReceiver = (function() {
var calls = Object.create(null);
var childProcessMessageName = location.host + ':child-process-message';
var onChildProcessMessage = function(ev) {
var msg = ev.data;
if ( !msg ) { return; }
var fn = calls[msg.fnName];
if ( typeof fn === 'function' ) {
return fn(msg);
}
};
if ( Services.ppmm ) {
Services.ppmm.addMessageListener(
childProcessMessageName,
onChildProcessMessage
);
}
cleanupTasks.push(function() {
if ( Services.ppmm ) {
Services.ppmm.removeMessageListener(
childProcessMessageName,
onChildProcessMessage
);
}
});
return calls;
})();
/******************************************************************************/
/******************************************************************************/ /******************************************************************************/
var httpObserver = { var httpObserver = {
@ -1865,7 +1920,11 @@ vAPI.net.registerListeners = function() {
var tabId = tabWatcher.tabIdFromTarget(e.target); var tabId = tabWatcher.tabIdFromTarget(e.target);
var sourceTabId = null; var sourceTabId = null;
// Popup candidate // Popup candidate: this code path is taken only for when a new top
// document loads, i.e. only once per document load. TODO: evaluate for
// popup filtering in an asynchrous manner -- it's not really required
// to evaluate on the spot. Still, there is currently no harm given
// this code path is typically taken only once per page load.
if ( details.openerURL ) { if ( details.openerURL ) {
for ( var browser of tabWatcher.browsers() ) { for ( var browser of tabWatcher.browsers() ) {
var URI = browser.currentURI; var URI = browser.currentURI;
@ -1899,8 +1958,9 @@ vAPI.net.registerListeners = function() {
} }
} }
//console.log('shouldLoadListener:', details.url); // We are being called synchronously from the content process, so we
// must return ASAP. The code below merely record the details of the
// request into a ring buffer for later retrieval by the HTTP observer.
var pendingReq = httpObserver.createPendingRequest(details.url); var pendingReq = httpObserver.createPendingRequest(details.url);
pendingReq.frameId = details.frameId; pendingReq.frameId = details.frameId;
pendingReq.parentFrameId = details.parentFrameId; pendingReq.parentFrameId = details.parentFrameId;

View file

@ -31,6 +31,13 @@
/******************************************************************************/ /******************************************************************************/
// Not all sandbox are given an rpc function, so assign a dummy one it is
// missing -- this avoids the need for constantly testing before use.
self.rpc = self.rpc || function(){};
/******************************************************************************/
var vAPI = self.vAPI = self.vAPI || {}; var vAPI = self.vAPI = self.vAPI || {};
vAPI.firefox = true; vAPI.firefox = true;
vAPI.sessionId = String.fromCharCode(Date.now() % 26 + 97) + vAPI.sessionId = String.fromCharCode(Date.now() % 26 + 97) +
@ -67,6 +74,30 @@ vAPI.shutdown = (function() {
/******************************************************************************/ /******************************************************************************/
(function() {
var hostname = location.hostname;
if ( !hostname ) {
return;
}
var filters = self.rpc({
fnName: 'getScriptTagFilters',
url: location.href,
hostname: hostname
});
if ( typeof filters !== 'string' || filters === '' ) {
return;
}
var reFilters = new RegExp(filters);
document.addEventListener('beforescriptexecute', function(ev) {
if ( reFilters.test(ev.target.textContent) ) {
ev.preventDefault();
ev.stopPropagation();
}
});
})();
/******************************************************************************/
vAPI.messaging = { vAPI.messaging = {
channels: {}, channels: {},
pending: {}, pending: {},
@ -254,6 +285,12 @@ MessagingChannel.prototype.send = function(message, callback) {
MessagingChannel.prototype.sendTo = function(message, toTabId, toChannel, callback) { MessagingChannel.prototype.sendTo = function(message, toTabId, toChannel, callback) {
var messaging = vAPI.messaging; var messaging = vAPI.messaging;
if ( !messaging ) {
if ( typeof callback === 'function' ) {
callback();
}
return;
}
// Too large a gap between the last request and the last response means // Too large a gap between the last request and the last response means
// the main process is no longer reachable: memory leaks and bad // the main process is no longer reachable: memory leaks and bad
// performance become a risk -- especially for long-lived, dynamic // performance become a risk -- especially for long-lived, dynamic

View file

@ -30,6 +30,7 @@
<script src="js/traffic.js"></script> <script src="js/traffic.js"></script>
<script src="js/contextmenu.js"></script> <script src="js/contextmenu.js"></script>
<script src="js/reverselookup.js"></script> <script src="js/reverselookup.js"></script>
<script src="js/rpcreceiver.js"></script>
<script src="js/start.js"></script> <script src="js/start.js"></script>
</body> </body>
</html> </html>

View file

@ -128,15 +128,7 @@ var netFilters = function(details) {
//console.debug('document.querySelectorAll("%s") = %o', text, document.querySelectorAll(text)); //console.debug('document.querySelectorAll("%s") = %o', text, document.querySelectorAll(text));
}; };
var onBeforeScriptExecuteHandler = function(ev) {
if ( vAPI.reScriptTagRegex.test(ev.target.textContent) ) {
ev.preventDefault();
ev.stopPropagation();
}
};
var filteringHandler = function(details) { var filteringHandler = function(details) {
var value;
var styleTagCount = vAPI.styles.length; var styleTagCount = vAPI.styles.length;
vAPI.skipCosmeticFiltering = !details || details.skipCosmeticFiltering; vAPI.skipCosmeticFiltering = !details || details.skipCosmeticFiltering;
@ -147,11 +139,6 @@ var filteringHandler = function(details) {
if ( details.netHide.length !== 0 ) { if ( details.netHide.length !== 0 ) {
netFilters(details); netFilters(details);
} }
value = details.scriptTagRegex;
if ( typeof value === 'string' && value.length !== 0 ) {
vAPI.reScriptTagRegex = new RegExp(value);
document.addEventListener('beforescriptexecute', onBeforeScriptExecuteHandler);
}
// The port will never be used again at this point, disconnecting allows // The port will never be used again at this point, disconnecting allows
// the browser to flush this script from memory. // the browser to flush this script from memory.
} }

View file

@ -237,7 +237,7 @@ var FilterParser = function() {
this.invalid = false; this.invalid = false;
this.cosmetic = true; this.cosmetic = true;
this.reParser = /^\s*([^#]*)(##|#@#)(.+)\s*$/; this.reParser = /^\s*([^#]*)(##|#@#)(.+)\s*$/;
this.reScriptSelectorParser = /^script:contains\(\/.+?\/\)$/; this.reScriptContains = /^script:contains\(.+?\)$/;
}; };
/******************************************************************************/ /******************************************************************************/
@ -286,16 +286,20 @@ FilterParser.prototype.parse = function(s) {
// Script tag filters: pre-process them so that can be used with minimal // Script tag filters: pre-process them so that can be used with minimal
// overhead in the content script. // overhead in the content script.
if ( // Example: focus.de##script:contains(/uabInject/)
this.suffix.charAt(0) === 's' && if ( this.suffix.charAt(0) === 's' && this.reScriptContains.test(this.suffix) ) {
this.reScriptSelectorParser.test(this.suffix)
) {
// Currently supported only as non-generic selector. // Currently supported only as non-generic selector.
if ( this.prefix.length === 0 ) { if ( this.prefix.length === 0 ) {
this.invalid = true; this.invalid = true;
return this; return this;
} }
this.suffix = 'script//:' + this.suffix.slice(17, -2).replace(/\\/g, '\\'); var suffix = this.suffix;
this.suffix = 'script//:';
if ( suffix.charAt(16) !== '/' || suffix.slice(-2) !== '/)' ) {
this.suffix += suffix.slice(16, -1).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
} else {
this.suffix += suffix.slice(17, -2).replace(/\\/g, '\\');
}
} }
this.unhide = matches[2].charAt(1) === '@' ? 1 : 0; this.unhide = matches[2].charAt(1) === '@' ? 1 : 0;
@ -1257,7 +1261,6 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request) {
cosmeticHide: [], cosmeticHide: [],
cosmeticDonthide: [], cosmeticDonthide: [],
netHide: [], netHide: [],
scriptTagRegex: this.retrieveScriptTagRegex(domain, hostname),
netCollapse: µb.userSettings.collapseBlocked netCollapse: µb.userSettings.collapseBlocked
}; };