aboutsummaryrefslogtreecommitdiffstats
path: root/src/js
diff options
context:
space:
mode:
authorgorhill <rhill@raymondhill.net>2015-03-29 12:13:28 -0400
committergorhill <rhill@raymondhill.net>2015-03-29 12:13:28 -0400
commit923019794d8586ceb01e33ca714de7a63f105748 (patch)
treefa6cb5ee174755a77234a0506f1d3439715ff905 /src/js
parent8d61a04b3e48e112e139704aa625a6e8410b8b04 (diff)
downloaduBlock-923019794d8586ceb01e33ca714de7a63f105748.zip
uBlock-923019794d8586ceb01e33ca714de7a63f105748.tar.gz
uBlock-923019794d8586ceb01e33ca714de7a63f105748.tar.bz2
this fixes #618
Diffstat (limited to 'src/js')
-rw-r--r--src/js/contentscript-end.js415
-rw-r--r--src/js/messaging.js89
2 files changed, 232 insertions, 272 deletions
diff --git a/src/js/contentscript-end.js b/src/js/contentscript-end.js
index 4729c4a..74d6f82 100644
--- a/src/js/contentscript-end.js
+++ b/src/js/contentscript-end.js
@@ -84,6 +84,169 @@ var messager = vAPI.messaging.channel('contentscript-end.js');
})();
/******************************************************************************/
+/******************************************************************************/
+
+// https://github.com/gorhill/uBlock/issues/7
+
+var uBlockCollapser = (function() {
+ var timer = null;
+ var requestId = 1;
+ var newRequests = [];
+ var pendingRequests = {};
+ var pendingRequestCount = 0;
+ var srcProps = {
+ 'embed': 'src',
+ 'iframe': 'src',
+ 'img': 'src',
+ 'object': 'data'
+ };
+
+ var PendingRequest = function(target, tagName, attr) {
+ this.id = requestId++;
+ this.target = target;
+ this.tagName = tagName;
+ this.attr = attr;
+ pendingRequests[this.id] = this;
+ pendingRequestCount += 1;
+ };
+
+ // Because a while ago I have observed constructors are faster than
+ // literal object instanciations.
+ var BouncingRequest = function(id, tagName, url) {
+ this.id = id;
+ this.tagName = tagName;
+ this.url = url;
+ this.collapse = false;
+ };
+
+ var onProcessed = function(requests) {
+ if ( requests === null || Array.isArray(requests) === false ) {
+ return;
+ }
+ var selectors = [];
+ var i = requests.length;
+ var request, entry, target, value;
+ while ( i-- ) {
+ request = requests[i];
+ if ( pendingRequests.hasOwnProperty(request.id) === false ) {
+ continue;
+ }
+ entry = pendingRequests[request.id];
+ delete pendingRequests[request.id];
+ pendingRequestCount -= 1;
+
+ // https://github.com/gorhill/uBlock/issues/869
+ if ( !request.collapse ) {
+ continue;
+ }
+
+ target = entry.target;
+
+ // https://github.com/gorhill/uBlock/issues/399
+ // Never remove elements from the DOM, just hide them
+ target.style.setProperty('display', 'none', 'important');
+
+ // https://github.com/gorhill/uBlock/issues/1048
+ // Use attribute to construct CSS rule
+ if ( value = target.getAttribute(entry.attr) ) {
+ selectors.push(entry.tagName + '[' + entry.attr + '="' + value + '"]');
+ }
+ }
+ if ( selectors.length !== 0 ) {
+ messager.send({
+ what: 'injectedSelectors',
+ type: 'net',
+ hostname: window.location.hostname,
+ selectors: selectors
+ });
+ }
+ // Renew map: I believe that even if all properties are deleted, an
+ // object will still use more memory than a brand new one.
+ if ( pendingRequestCount === 0 ) {
+ pendingRequests = {};
+ }
+ };
+
+ var send = function() {
+ timer = null;
+ messager.send({
+ what: 'filterRequests',
+ pageURL: window.location.href,
+ pageHostname: window.location.hostname,
+ requests: newRequests
+ }, onProcessed);
+ newRequests = [];
+ };
+
+ var process = function(delay) {
+ if ( newRequests.length === 0 ) {
+ return;
+ }
+ if ( delay === 0 ) {
+ clearTimeout(timer);
+ send();
+ } else if ( timer === null ) {
+ timer = setTimeout(send, delay || 20);
+ }
+ };
+
+ // If needed eventually, we could listen to `src` attribute changes
+ // for iframes.
+
+ var add = function(target) {
+ var tagName = target.localName;
+ var prop = srcProps[tagName];
+ if ( prop === undefined ) {
+ return;
+ }
+ // https://github.com/gorhill/uBlock/issues/174
+ // Do not remove fragment from src URL
+ var src = target[prop];
+ if ( typeof src !== 'string' || src === '' ) {
+ return;
+ }
+ if ( src.lastIndexOf('http', 0) !== 0 ) {
+ return;
+ }
+ var req = new PendingRequest(target, tagName, prop);
+ newRequests.push(new BouncingRequest(req.id, tagName, src));
+ };
+
+ var addIFrame = function(iframe) {
+ var src = iframe.src;
+ // TODO: niject content script in `about:blank` as well.
+ if ( src === '' || typeof src !== 'string' ) {
+ return;
+ }
+ if ( src.lastIndexOf('http', 0) !== 0 ) {
+ return;
+ }
+ var req = new PendingRequest(iframe, 'iframe', 'src');
+ newRequests.push(new BouncingRequest(req.id, 'iframe', src));
+ };
+
+ var iframesFromNode = function(node) {
+ if ( node.localName === 'iframe' ) {
+ add(node);
+ }
+ var iframes = node.querySelectorAll('iframe');
+ var i = iframes.length;
+ while ( i-- ) {
+ addIFrame(iframes[i]);
+ }
+ process();
+ };
+
+ return {
+ add: add,
+ addIFrame: addIFrame,
+ iframesFromNode: iframesFromNode,
+ process: process
+ };
+})();
+
+/******************************************************************************/
+/******************************************************************************/
// Cosmetic filters
@@ -298,7 +461,7 @@ var messager = vAPI.messaging.channel('contentscript-end.js');
}
}
// Candidate 2 = specific form
- selector = node.tagName.toLowerCase() + selector;
+ selector = node.localName + selector;
if ( generics.hasOwnProperty(selector) ) {
if ( injectedSelectors.hasOwnProperty(selector) === false ) {
injectedSelectors[selector] = true;
@@ -461,20 +624,22 @@ var messager = vAPI.messaging.channel('contentscript-end.js');
return;
}
+ // https://github.com/gorhill/uBlock/issues/618
+ // Following is to observe dynamically added iframes:
+ // - On Firefox, the iframes fails to fire a `load` event
+
var ignoreTags = {
'link': true,
- 'LINK': true,
'script': true,
- 'SCRIPT': true,
- 'style': true,
- 'STYLE': true
+ 'style': true
};
// Added node lists will be cumulated here before being processed
var addedNodeLists = [];
var addedNodeListsTimer = null;
+ var collapser = uBlockCollapser;
- var mutationObservedHandler = function() {
+ var treeMutationObservedHandler = function() {
var nodeList, iNode, node;
while ( nodeList = addedNodeLists.pop() ) {
iNode = nodeList.length;
@@ -483,10 +648,11 @@ var messager = vAPI.messaging.channel('contentscript-end.js');
if ( node.nodeType !== 1 ) {
continue;
}
- if ( ignoreTags.hasOwnProperty(node.tagName) ) {
+ if ( ignoreTags.hasOwnProperty(node.localName) ) {
continue;
}
contextNodes.push(node);
+ collapser.iframesFromNode(node);
}
}
addedNodeListsTimer = null;
@@ -512,7 +678,7 @@ var messager = vAPI.messaging.channel('contentscript-end.js');
// I arbitrarily chose 100 ms for now:
// I have to compromise between the overhead of processing too few
// nodes too often and the delay of many nodes less often.
- addedNodeListsTimer = setTimeout(mutationObservedHandler, 100);
+ addedNodeListsTimer = setTimeout(treeMutationObservedHandler, 100);
}
};
@@ -529,134 +695,17 @@ var messager = vAPI.messaging.channel('contentscript-end.js');
// Permanent
-(function() {
- // https://github.com/gorhill/uBlock/issues/683
- // Instead of a closure we use a map to remember the element to collapse
- var filterRequestId = 1;
- var filterRequests = {};
+// Listener to collapse blocked resources.
+// - Future requests not blocked yet
+// - Elements dynamically added to the page
+// - Elements which resource URL changes
- var FilterRequest = function(target, tagName, attr) {
- this.id = filterRequestId++;
- this.target = target;
- this.tagName = tagName;
- this.attr = attr;
- };
-
- FilterRequest.send = function(target, tagName, prop, src) {
- var req = new FilterRequest(target, tagName, prop);
- filterRequests[req.id] = req;
- messager.send(
- {
- what: 'filterRequest',
- id: req.id,
- tagName: tagName,
- requestURL: src,
- pageHostname: window.location.hostname,
- pageURL: window.location.href
- },
- onAnswerReceived
- );
- };
-
- // Process answer: collapse, or do nothing.
-
- var onAnswerReceived = function(details) {
- // This should not happen under normal circumstances. It probably can
- // happen if the extension is disabled though.
- if ( typeof details !== 'object' || details === null ) {
- return;
- }
-
- // This should definitely not happen
- if ( filterRequests.hasOwnProperty(details.id) === false ) {
- return;
- }
-
- var req = filterRequests[details.id];
- delete filterRequests[details.id];
-
- // https://github.com/gorhill/uBlock/issues/869
- if ( details.collapse !== true ) {
- return;
- }
-
- //console.log('contentscript-end.js > onAnswerReceived(%o)', req);
-
- // If `!important` is not there, going back using history will
- // likely cause the hidden element to re-appear.
- // https://github.com/gorhill/uBlock/issues/399
-
- // Never remove elements from the DOM, just hide them
- req.target.style.setProperty('display', 'none', 'important');
-
- // https://github.com/gorhill/uBlock/issues/1048
- // We need to use the atrtibute value for the CSS rule
- var value = req.target.getAttribute(req.attr);
- if ( !value ) {
- return;
- }
-
- messager.send({
- what: 'injectedSelectors',
- type: 'net',
- hostname: window.location.hostname,
- selectors: req.tagName + '[' + req.attr + '="' + value + '"]'
- });
- };
-
- // https://github.com/gorhill/uBlock/issues/174
- // Do not remove fragment from src URL
-
- // TODO: Find out whether trying to send more than one filter request per
- // message is worth it.
-
- var onResource = function(target, dict) {
- if ( !target ) {
- return;
- }
- var tagName = target.tagName.toLowerCase();
- var prop = dict[tagName];
- if ( prop === undefined ) {
- return;
- }
- var src = target[prop];
- if ( typeof src !== 'string' || src === '' ) {
- return;
- }
- if ( src.lastIndexOf('http', 0) !== 0 ) {
- return;
- }
- FilterRequest.send(target, tagName, prop, src);
- };
-
- // Listeners to mop up whatever is otherwise missed:
- // - Future requests not blocked yet
- // - Elements dynamically added to the page
- // - Elements which resource URL changes
-
- var loadedElements = {
- 'iframe': 'src'
- };
-
- var failedElements = {
- 'img': 'src',
- 'input': 'src',
- 'object': 'data'
- };
-
- var onResourceLoaded = function(ev) {
- //console.debug('onResourceLoaded(%o)', ev);
- onResource(ev.target, loadedElements);
- };
-
- var onResourceFailed = function(ev) {
- //console.debug('onResourceFailed(%o)', ev);
- onResource(ev.target, failedElements);
- };
-
- document.addEventListener('load', onResourceLoaded, true);
- document.addEventListener('error', onResourceFailed, true);
-})();
+var onResourceFailed = function(ev) {
+ //console.debug('onResourceFailed(%o)', ev);
+ uBlockCollapser.add(ev.target);
+ uBlockCollapser.process();
+};
+document.addEventListener('error', onResourceFailed, true);
/******************************************************************************/
/******************************************************************************/
@@ -666,88 +715,30 @@ var messager = vAPI.messaging.channel('contentscript-end.js');
// Executed only once
(function() {
- var srcProps = {
- 'embed': 'src',
- 'iframe': 'src',
- 'img': 'src',
- 'object': 'data'
- };
- var elements = [];
+ var collapser = uBlockCollapser;
+ var elems, i, elem;
- var onAnswerReceived = function(details) {
- if ( typeof details !== 'object' || details === null ) {
- return;
- }
- var collapse = details.collapse;
+ elems = document.querySelectorAll('embed, object');
+ i = elems.length;
+ while ( i-- ) {
+ collapser.add(elems[i]);
+ }
- // https://github.com/gorhill/uBlock/issues/869
- if ( collapse !== true ) {
- return;
+ elems = document.querySelectorAll('img');
+ i = elems.length;
+ while ( i-- ) {
+ elem = elems[i];
+ if ( elem.complete ) {
+ collapser.add(elem);
}
+ }
- var requests = details.requests;
- var selectors = [];
- var i = requests.length;
- var request, elem, attr, value;
- while ( i-- ) {
- request = requests[i];
- elem = elements[request.index];
- // https://github.com/gorhill/uBlock/issues/399
- // Never remove elements from the DOM, just hide them
- elem.style.setProperty('display', 'none', 'important');
- // https://github.com/gorhill/uBlock/issues/1048
- // Use attribute to construct CSS rule
- attr = srcProps[request.tagName];
- if ( value = elem.getAttribute(attr) ) {
- selectors.push(request.tagName + '[' + attr + '="' + value + '"]');
- }
- }
- if ( selectors.length !== 0 ) {
- messager.send({
- what: 'injectedSelectors',
- type: 'net',
- hostname: window.location.hostname,
- selectors: selectors
- });
- }
- };
-
- var requests = [];
- var tagNames = ['embed','iframe','img','object'];
- var elementIndex = 0;
- var tagName, elems, i, elem, prop, src;
- while ( tagName = tagNames.pop() ) {
- elems = document.getElementsByTagName(tagName);
- i = elems.length;
- while ( i-- ) {
- elem = elems[i];
- prop = srcProps[tagName];
- if ( prop === undefined ) {
- continue;
- }
- src = elem[prop];
- if ( typeof src !== 'string' || src === '' ) {
- continue;
- }
- if ( src.lastIndexOf('http', 0) !== 0 ) {
- continue;
- }
- requests.push({
- index: elementIndex,
- tagName: tagName,
- url: src
- });
- elements[elementIndex] = elem;
- elementIndex += 1;
- }
+ elems = document.querySelectorAll('iframe');
+ i = elems.length;
+ while ( i-- ) {
+ collapser.addIFrame(elems[i]);
}
- var details = {
- what: 'filterRequests',
- pageURL: window.location.href,
- pageHostname: window.location.hostname,
- requests: requests
- };
- messager.send(details, onAnswerReceived);
+ collapser.process(0);
})();
/******************************************************************************/
diff --git a/src/js/messaging.js b/src/js/messaging.js
index 82f8e7e..05b9ce9 100644
--- a/src/js/messaging.js
+++ b/src/js/messaging.js
@@ -435,60 +435,42 @@ var tagNameToRequestTypeMap = {
// Evaluate many requests
var filterRequests = function(pageStore, details) {
+ var requests = details.requests;
+ if ( !pageStore || !pageStore.getNetFilteringSwitch() ) {
+ return requests;
+ }
+ if ( µb.userSettings.collapseBlocked === false ) {
+ return requests;
+ }
+
+ //console.debug('messaging.js/contentscript-end.js: processing %d requests', requests.length);
+
var µburi = µb.URI;
var isBlockResult = µb.isBlockResult;
// Create evaluation context
- details.pageHostname = vAPI.punycodeHostname(details.pageHostname);
- details.pageDomain = µburi.domainFromHostname(details.pageHostname);
- details.rootHostname = pageStore.rootHostname;
- details.rootDomain = pageStore.rootDomain;
- details.requestHostname = '';
-
- var inRequests = details.requests;
- var outRequests = [];
+ var context = {
+ pageHostname: vAPI.punycodeHostname(details.pageHostname),
+ pageDomain: µburi.domainFromHostname(details.pageHostname),
+ rootHostname: pageStore.rootHostname,
+ rootDomain: pageStore.rootDomain,
+ requestURL: '',
+ requestHostname: '',
+ requestType: ''
+ };
+
var request;
- var i = inRequests.length;
+ var i = requests.length;
while ( i-- ) {
- request = inRequests[i];
- if ( tagNameToRequestTypeMap.hasOwnProperty(request.tagName) === false ) {
- continue;
- }
- details.requestURL = vAPI.punycodeURL(request.url);
- details.requestHostname = µburi.hostnameFromURI(details.requestURL);
- details.requestType = tagNameToRequestTypeMap[request.tagName];
- if ( isBlockResult(pageStore.filterRequest(details)) ) {
- outRequests.push(request);
+ request = requests[i];
+ context.requestURL = vAPI.punycodeURL(request.url);
+ context.requestHostname = µburi.hostnameFromURI(request.url);
+ context.requestType = tagNameToRequestTypeMap[request.tagName];
+ if ( isBlockResult(pageStore.filterRequest(context)) ) {
+ request.collapse = true;
}
}
- return {
- collapse: µb.userSettings.collapseBlocked,
- requests: outRequests
- };
-};
-
-/******************************************************************************/
-
-// Evaluate a single request
-
-var filterRequest = function(pageStore, details) {
- if ( tagNameToRequestTypeMap.hasOwnProperty(details.tagName) === false ) {
- return;
- }
- var µburi = µb.URI;
- details.pageHostname = vAPI.punycodeHostname(details.pageHostname);
- details.pageDomain = µburi.domainFromHostname(details.pageHostname);
- details.rootHostname = pageStore.rootHostname;
- details.rootDomain = pageStore.rootDomain;
- details.requestURL = vAPI.punycodeURL(details.requestURL);
- details.requestHostname = µburi.hostnameFromURI(details.requestURL);
- details.requestType = tagNameToRequestTypeMap[details.tagName];
- if ( µb.isBlockResult(pageStore.filterRequest(details)) ) {
- return {
- collapse: µb.userSettings.collapseBlocked,
- id: details.id
- };
- }
+ return requests;
};
/******************************************************************************/
@@ -521,20 +503,7 @@ var onMessage = function(details, sender, callback) {
// Evaluate many requests
case 'filterRequests':
- if ( pageStore && pageStore.getNetFilteringSwitch() ) {
- response = filterRequests(pageStore, details);
- }
- break;
-
- // Evaluate a single request
- case 'filterRequest':
- if ( pageStore && pageStore.getNetFilteringSwitch() ) {
- // console.log('contentscript-end.js > filterRequest(%o)', details);
- response = filterRequest(pageStore, details);
- }
- if ( response === undefined ) {
- response = { id: details.id };
- }
+ response = filterRequests(pageStore, details);
break;
default: