From 95ec573141f6d61a36ba4a054401e6d1de259519 Mon Sep 17 00:00:00 2001 From: gorhill Date: Sat, 24 Sep 2016 14:36:08 -0400 Subject: [PATCH] fix #2014 --- platform/firefox/frameModule.js | 153 ++++++++++++++++++++++------ platform/firefox/frameScript.js | 31 +++--- platform/firefox/vapi-background.js | 45 ++++---- platform/firefox/vapi-client.js | 13 +-- src/js/3p-filters.js | 2 +- src/js/cosmetic-filtering.js | 6 ++ src/js/rpcreceiver.js | 19 ++-- src/js/storage.js | 3 +- tools/make-firefox.sh | 1 + 9 files changed, 190 insertions(+), 83 deletions(-) diff --git a/platform/firefox/frameModule.js b/platform/firefox/frameModule.js index 1b365e292..3ce8d8e2d 100644 --- a/platform/firefox/frameModule.js +++ b/platform/firefox/frameModule.js @@ -19,12 +19,18 @@ Home: https://github.com/gorhill/uBlock */ +/* exported processObserver */ + 'use strict'; /******************************************************************************/ // https://github.com/gorhill/uBlock/issues/800 -this.EXPORTED_SYMBOLS = ['contentObserver', 'LocationChangeListener']; +this.EXPORTED_SYMBOLS = [ + 'contentObserver', + 'processObserver', + 'LocationChangeListener' +]; const {interfaces: Ci, utils: Cu} = Components; const {Services} = Cu.import('resource://gre/modules/Services.jsm', null); @@ -64,20 +70,94 @@ const getMessageManager = function(win) { /******************************************************************************/ -const getChildProcessMessageManager = function() { - var svc = Services; - if ( !svc ) { - return; - } - var cpmm = svc.cpmm; - if ( cpmm ) { - return cpmm; - } - cpmm = Components.classes['@mozilla.org/childprocessmessagemanager;1']; - if ( cpmm ) { - return cpmm.getService(Ci.nsISyncMessageSender); - } -}; +// https://github.com/gorhill/uBlock/issues/2014 +// Have a dictionary of hostnames for which there are script tag filters. This +// allow for coarse-testing before firing a synchronous message to the +// parent process. Script tag filters are not very common, so this allows +// to skip the blocking of the child process most of the time. + +var scriptTagFilterer = (function() { + var scriptTagHostnames; + + var getCpmm = function() { + var svc = Services; + if ( !svc ) { return; } + var cpmm = svc.cpmm; + if ( cpmm ) { return cpmm; } + cpmm = Components.classes['@mozilla.org/childprocessmessagemanager;1']; + if ( cpmm ) { return cpmm.getService(Ci.nsISyncMessageSender); } + }; + + var listener = function(message) { + var details; + try { + details = JSON.parse(message.data); + } catch (ex) { + } + if ( !details || !details.msg ) { return; } + if (details.msg.what === 'staticFilteringDataChanged' ) { + reset(); + } + }; + + var getScriptTagHostnames = function() { + if ( scriptTagHostnames ) { + return scriptTagHostnames; + } + var cpmm = getCpmm(); + if ( !cpmm ) { return; } + var r = cpmm.sendSyncMessage(rpcEmitterName, { fnName: 'getScriptTagHostnames' }); + if ( Array.isArray(r) && Array.isArray(r[0]) ) { + scriptTagHostnames = new Set(r[0]); + } + return scriptTagHostnames; + }; + + var getScriptTagFilters = function(details) { + let cpmm = getCpmm(); + if ( !cpmm ) { return; } + let r = cpmm.sendSyncMessage(rpcEmitterName, { + fnName: 'getScriptTagFilters', + rootURL: details.rootURL, + frameURL: details.frameURL, + frameHostname: details.frameHostname + }); + if ( Array.isArray(r) ) { + return r[0]; + } + }; + + var regexFromHostname = function(details) { + // If target hostname has no script tag filter, no point querying + // chrome process. + var hostnames = getScriptTagHostnames(); + if ( !hostnames ) { return; } + var hn = details.frameHostname, pos, entity; + for (;;) { + if ( hostnames.has(hn) ) { + return getScriptTagFilters(details); + } + pos = hn.indexOf('.'); + if ( pos === -1 ) { break; } + entity = hn.slice(0, pos) + '.*'; + if ( hostnames.has(entity) ) { + return getScriptTagFilters(details); + } + hn = hn.slice(pos + 1); + if ( hn === '' ) { break; } + } + }; + + var reset = function() { + scriptTagHostnames = undefined; + }; + + return { + get: regexFromHostname, + listener: listener, + reset: reset + }; +})(); /******************************************************************************/ @@ -305,18 +385,9 @@ var contentObserver = { wantXHRConstructor: false }); - if ( getChildProcessMessageManager() ) { - sandbox.rpc = function(details) { - var cpmm = getChildProcessMessageManager(); - if ( !cpmm ) { return; } - var r = cpmm.sendSyncMessage(rpcEmitterName, details); - if ( Array.isArray(r) ) { - return r[0]; - } - }; - } else { - sandbox.rpc = function() {}; - } + sandbox.getScriptTagFilters = function(details) { + return scriptTagFilterer.get(details); + }; sandbox.injectScript = function(script) { let svc = Services; @@ -344,6 +415,8 @@ var contentObserver = { } }; + sandbox.topContentScript = win === win.top; + // The goal is to have content scripts removed from web pages. This // helps remove traces of uBlock from memory when disabling/removing // the addon. @@ -353,15 +426,15 @@ var contentObserver = { sandbox.outerShutdown = function() { sandbox.removeMessageListener(); sandbox.addMessageListener = + sandbox.getScriptTagFilters = sandbox.injectCSS = sandbox.injectScript = sandbox.outerShutdown = sandbox.removeCSS = sandbox.removeMessageListener = - sandbox.rpc = sandbox.sendAsyncMessage = function(){}; sandbox.vAPI = {}; - messager = null; + messager = sandbox = null; }; } else { @@ -380,13 +453,21 @@ var contentObserver = { callback(message.data); }; + sandbox._broadcastListener_ = function(message) { + // https://github.com/gorhill/uBlock/issues/2014 + if ( sandbox.topContentScript ) { + scriptTagFilterer.listener(message); + } + callback(message.data); + }; + messager.addMessageListener( sandbox._sandboxId_, sandbox._messageListener_ ); messager.addMessageListener( hostName + ':broadcast', - sandbox._messageListener_ + sandbox._broadcastListener_ ); }; @@ -401,13 +482,13 @@ var contentObserver = { ); messager.removeMessageListener( hostName + ':broadcast', - sandbox._messageListener_ + sandbox._broadcastListener_ ); } catch (ex) { // It throws sometimes, mostly when the popup closes } - sandbox._messageListener_ = null; + sandbox._messageListener_ = sandbox._broadcastListener_ = null; }; return sandbox; @@ -498,6 +579,14 @@ var contentObserver = { /******************************************************************************/ +var processObserver = { + start: function() { + scriptTagFilterer.reset(); + } +}; + +/******************************************************************************/ + var LocationChangeListener = function(docShell, webProgress) { var mm = docShell.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIContentFrameMessageManager); diff --git a/platform/firefox/frameScript.js b/platform/firefox/frameScript.js index f30b654c9..260f05557 100644 --- a/platform/firefox/frameScript.js +++ b/platform/firefox/frameScript.js @@ -23,21 +23,16 @@ // https://developer.mozilla.org/en-US/Firefox/Multiprocess_Firefox/Frame_script_environment -(function() { +(function(context) { 'use strict'; - let {LocationChangeListener} = Components.utils.import( - Components.stack.filename.replace('Script', 'Module'), - null - ); - - if ( !this.docShell ) { + if ( !context.docShell ) { return; } - let webProgress = this.docShell - .QueryInterface(Components.interfaces.nsIInterfaceRequestor) - .getInterface(Components.interfaces.nsIWebProgress); + let webProgress = context.docShell + .QueryInterface(Components.interfaces.nsIInterfaceRequestor) + .getInterface(Components.interfaces.nsIWebProgress); if ( !webProgress ) { return; } @@ -49,11 +44,19 @@ return; } + let {LocationChangeListener} = Components.utils.import( + Components.stack.filename.replace('Script', 'Module'), + null + ); + // https://github.com/gorhill/uBlock/issues/1444 // Apparently, on older versions of Firefox (31 and less), the same context - // is used for all extensions, hence we must use a unique variable name to - // ensure no collision. - this.ublock0LocationChangeListener = new LocationChangeListener(this.docShell, webProgress); -}).call(this); + // is used for all frame scripts, hence we must use a unique variable name + // to ensure no collision. + context.ublock0LocationChangeListener = new LocationChangeListener( + context.docShell, + webProgress + ); +})(this); /******************************************************************************/ diff --git a/platform/firefox/vapi-background.js b/platform/firefox/vapi-background.js index 5a7ed887e..2c1dc9a26 100644 --- a/platform/firefox/vapi-background.js +++ b/platform/firefox/vapi-background.js @@ -1760,16 +1760,15 @@ vAPI.messaging.setup = function(defaultHandler) { } this.defaultHandler = defaultHandler; - this.globalMessageManager.addMessageListener( + var gmm = this.globalMessageManager; + gmm.addMessageListener( location.host + ':background', this.onMessage ); - - this.globalMessageManager.loadFrameScript(this.frameScriptURL, true); + gmm.loadFrameScript(this.frameScriptURL, true); cleanupTasks.push(function() { var gmm = vAPI.messaging.globalMessageManager; - gmm.broadcastAsyncMessage( location.host + ':broadcast', JSON.stringify({ @@ -1778,13 +1777,11 @@ vAPI.messaging.setup = function(defaultHandler) { msg: { cmd: 'shutdownSandbox' } }) ); - gmm.removeDelayedFrameScript(vAPI.messaging.frameScriptURL); gmm.removeMessageListener( location.host + ':background', vAPI.messaging.onMessage ); - vAPI.messaging.defaultHandler = null; }); }; @@ -1820,8 +1817,9 @@ vAPI.messaging.broadcast = function(message) { // 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 calls = Object.create(null), + childProcessMessageName = location.host + ':child-process-message', + processScriptURL = vAPI.getURL('processScript.js'); var onChildProcessMessage = function(ev) { var msg = ev.data; @@ -1838,22 +1836,31 @@ vAPI.rpcReceiver = (function() { if ( ppmm ) { ppmm = ppmm.getService(Ci.nsIMessageListenerManager); } + if ( !ppmm ) { + return calls; + } } - if ( ppmm ) { - ppmm.addMessageListener( + // https://github.com/gorhill/uBlock/issues/2014 + // Not supported on older versions of Firefox. + if ( ppmm.loadProcessScript instanceof Function ) { + ppmm.loadProcessScript(processScriptURL, true); + } + + ppmm.addMessageListener( + childProcessMessageName, + onChildProcessMessage + ); + + cleanupTasks.push(function() { + if ( ppmm.removeDelayedProcessScript instanceof Function ) { + ppmm.removeDelayedProcessScript(processScriptURL); + } + + ppmm.removeMessageListener( childProcessMessageName, onChildProcessMessage ); - } - - cleanupTasks.push(function() { - if ( ppmm ) { - ppmm.removeMessageListener( - childProcessMessageName, - onChildProcessMessage - ); - } }); return calls; diff --git a/platform/firefox/vapi-client.js b/platform/firefox/vapi-client.js index a7662a804..1523989f0 100644 --- a/platform/firefox/vapi-client.js +++ b/platform/firefox/vapi-client.js @@ -45,13 +45,6 @@ if ( document instanceof HTMLDocument === false ) { /******************************************************************************/ -// Not all sandboxes are given an rpc function, so assign a dummy one if it is -// missing -- this avoids the need for testing before use. - -self.rpc = self.rpc || function(){}; - -/******************************************************************************/ - var vAPI = self.vAPI = self.vAPI || {}; /******************************************************************************/ @@ -154,12 +147,14 @@ vAPI.shutdown = (function() { /******************************************************************************/ (function() { + if ( !self.getScriptTagFilters ) { + return; + } var hostname = location.hostname; if ( !hostname ) { return; } - var filters = self.rpc({ - fnName: 'getScriptTagFilters', + var filters = self.getScriptTagFilters({ rootURL: self.location.href, frameURL: self.location.href, frameHostname: hostname diff --git a/src/js/3p-filters.js b/src/js/3p-filters.js index 54bfda204..0b960e81c 100644 --- a/src/js/3p-filters.js +++ b/src/js/3p-filters.js @@ -42,7 +42,7 @@ var hasCachedContent = false; var onMessage = function(msg) { switch ( msg.what ) { - case 'allFilterListsReloaded': + case 'staticFilteringDataChanged': renderFilterLists(); break; diff --git a/src/js/cosmetic-filtering.js b/src/js/cosmetic-filtering.js index 8859530bc..ee530ee74 100644 --- a/src/js/cosmetic-filtering.js +++ b/src/js/cosmetic-filtering.js @@ -1237,6 +1237,12 @@ FilterContainer.prototype.createScriptTagFilter = function(hash, hostname, selec /******************************************************************************/ +FilterContainer.prototype.retrieveScriptTagHostnames = function() { + return Object.keys(this.scriptTagFilters); +}; + +/******************************************************************************/ + FilterContainer.prototype.retrieveScriptTagRegex = function(domain, hostname) { if ( this.scriptTagFilterCount === 0 ) { return; diff --git a/src/js/rpcreceiver.js b/src/js/rpcreceiver.js index 48d1068ff..681d0f7d0 100644 --- a/src/js/rpcreceiver.js +++ b/src/js/rpcreceiver.js @@ -1,7 +1,7 @@ /******************************************************************************* uBlock Origin - a browser extension to block requests. - Copyright (C) 2015 Raymond Hill + Copyright (C) 2015-2016 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 @@ -19,14 +19,12 @@ Home: https://github.com/gorhill/uBlock */ -/* global vAPI, µBlock */ +'use strict'; /******************************************************************************/ (function() { -'use strict'; - /******************************************************************************/ if ( typeof vAPI.rpcReceiver !== 'object' ) { @@ -35,12 +33,19 @@ if ( typeof vAPI.rpcReceiver !== 'object' ) { /******************************************************************************/ +vAPI.rpcReceiver.getScriptTagHostnames = function() { + var µb = µBlock; + var cfe = µb.cosmeticFilteringEngine; + if ( !cfe ) { return; } + return cfe.retrieveScriptTagHostnames(); +}; + +/******************************************************************************/ + vAPI.rpcReceiver.getScriptTagFilters = function(details) { var µb = µBlock; var cfe = µb.cosmeticFilteringEngine; - if ( !cfe ) { - return; - } + if ( !cfe ) { return; } // Fetching the script tag filters first: assuming it is faster than // checking whether the site is whitelisted. var hostname = details.frameHostname; diff --git a/src/js/storage.js b/src/js/storage.js index 163409439..d5838bffa 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -406,7 +406,8 @@ //quickProfiler.stop(0); - vAPI.messaging.broadcast({ what: 'allFilterListsReloaded' }); + vAPI.messaging.broadcast({ what: 'staticFilteringDataChanged' }); + callback(); µb.selfieManager.create(); diff --git a/tools/make-firefox.sh b/tools/make-firefox.sh index b90814656..6199fb46a 100755 --- a/tools/make-firefox.sh +++ b/tools/make-firefox.sh @@ -25,6 +25,7 @@ cp platform/firefox/css/* $DES/css/ cp platform/firefox/polyfill.js $DES/js/ cp platform/firefox/vapi-*.js $DES/js/ cp platform/firefox/bootstrap.js $DES/ +cp platform/firefox/processScript.js $DES/ cp platform/firefox/frame*.js $DES/ cp -R platform/firefox/img $DES/ cp platform/firefox/chrome.manifest $DES/