From bc700e691cf97b5e4d1f00e055191fbab4a1330e Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Sun, 15 Mar 2020 08:45:17 -0400 Subject: [PATCH] Fix srcset handling in element picker Related issue: - https://github.com/uBlockOrigin/uBlock-issues/issues/923 Use current page's hostname instead of that of image URLs to decide whether to reset pattern union with previous picker sessions. The fixed issue arose from the fact that the page uses URLs from different origins in a single srcset attribute. --- src/js/scriptlets/element-picker.js | 227 +++++++++------------------- 1 file changed, 68 insertions(+), 159 deletions(-) diff --git a/src/js/scriptlets/element-picker.js b/src/js/scriptlets/element-picker.js index 71daae40f..c859c1144 100644 --- a/src/js/scriptlets/element-picker.js +++ b/src/js/scriptlets/element-picker.js @@ -26,94 +26,6 @@ /******************************************************************************/ /******************************************************************************/ -/*! http://mths.be/cssescape v0.2.1 by @mathias | MIT license */ -;(function(root) { - - if (!root.CSS) { - root.CSS = {}; - } - - var CSS = root.CSS; - - var InvalidCharacterError = function(message) { - this.message = message; - }; - InvalidCharacterError.prototype = new Error(); - InvalidCharacterError.prototype.name = 'InvalidCharacterError'; - - if (!CSS.escape) { - // http://dev.w3.org/csswg/cssom/#serialize-an-identifier - CSS.escape = function(value) { - var string = String(value); - var length = string.length; - var index = -1; - var codeUnit; - var result = ''; - var firstCodeUnit = string.charCodeAt(0); - while (++index < length) { - codeUnit = string.charCodeAt(index); - // Note: there’s no need to special-case astral symbols, surrogate - // pairs, or lone surrogates. - - // If the character is NULL (U+0000), then throw an - // `InvalidCharacterError` exception and terminate these steps. - if (codeUnit === 0x0000) { - throw new InvalidCharacterError( - 'Invalid character: the input contains U+0000.' - ); - } - - if ( - // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is - // U+007F, […] - (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit === 0x007F || - // If the character is the first character and is in the range [0-9] - // (U+0030 to U+0039), […] - (index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || - // If the character is the second character and is in the range [0-9] - // (U+0030 to U+0039) and the first character is a `-` (U+002D), […] - ( - index === 1 && - codeUnit >= 0x0030 && codeUnit <= 0x0039 && - firstCodeUnit === 0x002D - ) - ) { - // http://dev.w3.org/csswg/cssom/#escape-a-character-as-code-point - result += '\\' + codeUnit.toString(16) + ' '; - continue; - } - - // If the character is not handled by one of the above rules and is - // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or - // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to - // U+005A), or [a-z] (U+0061 to U+007A), […] - if ( - codeUnit >= 0x0080 || - codeUnit === 0x002D || - codeUnit === 0x005F || - codeUnit >= 0x0030 && codeUnit <= 0x0039 || - codeUnit >= 0x0041 && codeUnit <= 0x005A || - codeUnit >= 0x0061 && codeUnit <= 0x007A - ) { - // the character itself - result += string.charAt(index); - continue; - } - - // Otherwise, the escaped character. - // http://dev.w3.org/csswg/cssom/#escape-a-character - result += '\\' + string.charAt(index); - - } - return result; - }; - } - -}(self)); - -/******************************************************************************/ -/******************************************************************************/ - (( ) => { /******************************************************************************/ @@ -291,20 +203,28 @@ const mergeStrings = function(urls) { } else { result.push(diff[1].replace(/\n+/g, '')); } + merged = result.join(''); } - // Keep usage of wildcards to a sane level, too many of them can cause - // high overhead filters - merged = - result.join('') - .replace(/\*+$/, '') - .replace(/\*{2,}/g, '*') - .replace(/([^*]{1,2}\*)(?:[^*]{1,2}\*)+/g, '$1'); } + // Keep usage of wildcards to a sane level, too many of them can cause + // high overhead filters + merged = merged.replace(/^\*+$/, '') + .replace(/\*{2,}/g, '*') + .replace(/([^*]{1,3}\*)(?:[^*]{1,3}\*)+/g, '$1'); return merged; }; /******************************************************************************/ +// Remove fragment part from a URL. + +const trimFragmentFromURL = function(url) { + const pos = url.indexOf('#'); + return pos !== -1 ? url.slice(0, pos) : url; +}; + +/******************************************************************************/ + // https://github.com/gorhill/uBlock/issues/1897 // Ignore `data:` URI, they can't be handled by an HTTP observer. @@ -313,7 +233,9 @@ const backgroundImageURLFromElement = function(elem) { const bgImg = style.backgroundImage || ''; const matches = /^url\((["']?)([^"']+)\1\)$/.exec(bgImg); const url = matches !== null && matches.length === 3 ? matches[2] : ''; - return url.lastIndexOf('data:', 0) === -1 ? url.slice(0, 1024) : ''; + return url.lastIndexOf('data:', 0) === -1 + ? trimFragmentFromURL(url.slice(0, 1024)) + : ''; }; /******************************************************************************/ @@ -322,84 +244,73 @@ const backgroundImageURLFromElement = function(elem) { // Limit returned string to 1024 characters. // Also, return only URLs which will be seen by an HTTP observer. -const resourceURLFromElement = function(elem) { +const resourceURLsFromElement = function(elem) { + const urls = []; const tagName = elem.localName; const prop = netFilter1stSources[tagName]; - if ( prop ) { - let src = ''; - { - let s = elem[prop]; - if ( typeof s === 'string' && /^https?:\/\//.test(s) ) { - src = s.slice(0, 1024); - } - } - if ( typeof elem.srcset === 'string' && elem.srcset !== '' ) { - const ss = []; - for ( let s of elem.srcset.split(',') ) { - s = s.trim(); - const pos = s.indexOf(' '); - if ( pos !== -1 ) { s = s.slice(0, pos); } - const parsedURL = new URL(s, document.baseURI); - if ( parsedURL.pathname.length > 1 ) { - ss.push(parsedURL.href); - } - } - if ( ss.length !== 0 ) { - if ( src !== '' ) { - ss.push(src); - } - src = mergeStrings(ss); - } - } - return src; + if ( prop === undefined ) { + const url = backgroundImageURLFromElement(elem); + if ( url !== '' ) { urls.push(url); } + return urls; } - return backgroundImageURLFromElement(elem); + { + const s = elem[prop]; + if ( typeof s === 'string' && /^https?:\/\//.test(s) ) { + urls.push(trimFragmentFromURL(s.slice(0, 1024))); + } + } + if ( typeof elem.srcset === 'string' && elem.srcset !== '' ) { + for ( let s of elem.srcset.split(',') ) { + s = s.trim(); + const pos = s.indexOf(' '); + if ( pos !== -1 ) { s = s.slice(0, pos); } + const parsedURL = new URL(s, document.baseURI); + if ( parsedURL.pathname.length > 1 ) { + urls.push(trimFragmentFromURL(parsedURL.href)); + } + } + } + return urls; }; /******************************************************************************/ -const netFilterFromUnion = function(toMergeURL, out) { - const parsedURL = new URL(toMergeURL, document.baseURI); - - toMergeURL = parsedURL.pathname + parsedURL.search; - +const netFilterFromUnion = function(patternIn, out) { // Reset reference filter when dealing with unrelated URLs + const currentHostname = self.location.hostname; if ( lastNetFilterUnion === '' || - parsedURL.host === '' || - parsedURL.host !== lastNetFilterHostname + currentHostname === '' || + currentHostname !== lastNetFilterHostname ) { - lastNetFilterHostname = parsedURL.host; - lastNetFilterUnion = toMergeURL; + lastNetFilterHostname = currentHostname; + lastNetFilterUnion = patternIn; vAPI.messaging.send('elementPicker', { what: 'elementPickerEprom', - lastNetFilterSession: lastNetFilterSession, - lastNetFilterHostname: lastNetFilterHostname, - lastNetFilterUnion: lastNetFilterUnion, + lastNetFilterSession, + lastNetFilterHostname, + lastNetFilterUnion, }); return; } // Related URLs - lastNetFilterHostname = parsedURL.host; - - let mergedURL = mergeStrings([ toMergeURL, lastNetFilterUnion ]); - if ( mergedURL !== '/*' && mergedURL !== toMergeURL ) { - const filter = '||' + lastNetFilterHostname + mergedURL; + lastNetFilterHostname = currentHostname; + let patternOut = mergeStrings([ patternIn, lastNetFilterUnion ]); + if ( patternOut !== '/*' && patternOut !== patternIn ) { + const filter = `||${patternOut}`; if ( out.indexOf(filter) === -1 ) { out.push(filter); } - } else { - mergedURL = toMergeURL; + lastNetFilterUnion = patternOut; } - lastNetFilterUnion = mergedURL; // Remember across element picker sessions vAPI.messaging.send('elementPicker', { what: 'elementPickerEprom', - lastNetFilterSession: lastNetFilterSession, - lastNetFilterHostname: lastNetFilterHostname, - lastNetFilterUnion: lastNetFilterUnion, + lastNetFilterSession, + lastNetFilterHostname, + lastNetFilterUnion, }); }; @@ -410,8 +321,8 @@ const netFilterFromUnion = function(toMergeURL, out) { const netFilterFromElement = function(elem) { if ( elem === null ) { return 0; } if ( elem.nodeType !== 1 ) { return 0; } - let src = resourceURLFromElement(elem); - if ( src === '' ) { return 0; } + const urls = resourceURLsFromElement(elem); + if ( urls.length === 0 ) { return 0; } if ( candidateElements.indexOf(elem) === -1 ) { candidateElements.push(elem); @@ -420,13 +331,11 @@ const netFilterFromElement = function(elem) { const candidates = netFilterCandidates; const len = candidates.length; - // Remove fragment - let pos = src.indexOf('#'); - if ( pos !== -1 ) { - src = src.slice(0, pos); + for ( let i = 0; i < urls.length; i++ ) { + urls[i] = urls[i].replace(/^https?:\/\//, ''); } + const pattern = mergeStrings(urls); - const filter = src.replace(/^https?:\/\//, '||'); if ( bestCandidateFilter === null ) { bestCandidateFilter = { @@ -436,16 +345,16 @@ const netFilterFromElement = function(elem) { }; } - candidates.push(filter); + candidates.push(`||${pattern}`); // Suggest a less narrow filter if possible - pos = filter.indexOf('?'); + const pos = pattern.indexOf('?'); if ( pos !== -1 ) { - candidates.push(filter.slice(0, pos)); + candidates.push(`||${pattern.slice(0, pos)}`); } // Suggest a filter which is a result of combining more than one URL. - netFilterFromUnion(src, candidates); + netFilterFromUnion(pattern, candidates); return candidates.length - len; };