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

View file

@ -19,13 +19,11 @@
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';
@ -52,25 +50,36 @@ let injectContentScripts = function(win) {
};
let onLoadCompleted = function() {
removeMessageListener('ublock0-load-completed', onLoadCompleted);
injectContentScripts(content);
context.removeMessageListener('ublock0-load-completed', onLoadCompleted);
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 wp = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
let wp = context.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
let dw = wp.DOMWindow;
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 = [];
// This must be updated manually, every time a new task is added/removed
var expectedNumberOfCleanups = 7;
var expectedNumberOfCleanups = 8;
window.addEventListener('unload', function() {
if ( typeof vAPI.app.onShutdown === 'function' ) {
@ -90,10 +90,11 @@ window.addEventListener('unload', function() {
}
// frameModule needs to be cleared too
var frameModuleURL = vAPI.getURL('frameModule.js');
var frameModule = {};
Cu.import(vAPI.getURL('frameModule.js'), frameModule);
Cu.import(frameModuleURL, frameModule);
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
var onOpen = function({target}) {
var tabId = tabIdFromTarget(target);
var browser = browserFromTabId(tabId);
vAPI.tabs.onNavigation({
frameId: 0,
tabId: tabId,
url: browser.currentURI.asciiSpec,
});
};
//var onOpen = function({target}) {
// var tabId = tabIdFromTarget(target);
// var browser = browserFromTabId(tabId);
// vAPI.tabs.onNavigation({
// frameId: 0,
// tabId: tabId,
// url: browser.currentURI.asciiSpec,
// });
//};
// https://developer.mozilla.org/en-US/docs/Web/Events/TabShow
var onShow = function({target}) {
@ -1208,7 +1209,7 @@ vAPI.messaging = {
return Cc['@mozilla.org/globalmessagemanager;1']
.getService(Ci.nsIMessageListenerManager);
},
frameScript: vAPI.getURL('frameScript.js'),
frameScriptURL: vAPI.getURL('frameScript.js'),
listeners: {},
defaultHandler: null,
NOOPFUNC: function(){},
@ -1438,7 +1439,7 @@ vAPI.messaging.setup = function(defaultHandler) {
this.onMessage
);
this.globalMessageManager.loadFrameScript(this.frameScript, true);
this.globalMessageManager.loadFrameScript(this.frameScriptURL, true);
cleanupTasks.push(function() {
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(
location.host + ':background',
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 = {
@ -1865,7 +1920,11 @@ vAPI.net.registerListeners = function() {
var tabId = tabWatcher.tabIdFromTarget(e.target);
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 ) {
for ( var browser of tabWatcher.browsers() ) {
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);
pendingReq.frameId = details.frameId;
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 || {};
vAPI.firefox = true;
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 = {
channels: {},
pending: {},
@ -254,6 +285,12 @@ MessagingChannel.prototype.send = function(message, callback) {
MessagingChannel.prototype.sendTo = function(message, toTabId, toChannel, callback) {
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
// the main process is no longer reachable: memory leaks and bad
// performance become a risk -- especially for long-lived, dynamic

View file

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

View file

@ -128,15 +128,7 @@ var netFilters = function(details) {
//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 value;
var styleTagCount = vAPI.styles.length;
vAPI.skipCosmeticFiltering = !details || details.skipCosmeticFiltering;
@ -147,11 +139,6 @@ var filteringHandler = function(details) {
if ( details.netHide.length !== 0 ) {
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 browser to flush this script from memory.
}

View file

@ -237,7 +237,7 @@ var FilterParser = function() {
this.invalid = false;
this.cosmetic = true;
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
// overhead in the content script.
if (
this.suffix.charAt(0) === 's' &&
this.reScriptSelectorParser.test(this.suffix)
) {
// Example: focus.de##script:contains(/uabInject/)
if ( this.suffix.charAt(0) === 's' && this.reScriptContains.test(this.suffix) ) {
// Currently supported only as non-generic selector.
if ( this.prefix.length === 0 ) {
this.invalid = true;
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;
@ -1257,7 +1261,6 @@ FilterContainer.prototype.retrieveDomainSelectors = function(request) {
cosmeticHide: [],
cosmeticDonthide: [],
netHide: [],
scriptTagRegex: this.retrieveScriptTagRegex(domain, hostname),
netCollapse: µb.userSettings.collapseBlocked
};