From 3730d7d128c647e2e092ef666bfbdbe7af0c7ddd Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Thu, 20 Dec 2018 17:29:39 -0500 Subject: [PATCH] Fix https://github.com/uBlockOrigin/uBlock-issues/issues/40 --- src/epicker.html | 1 + src/js/scriptlets/element-picker.js | 220 ++++++++++++++++------------ src/js/ublock.js | 10 ++ 3 files changed, 141 insertions(+), 90 deletions(-) diff --git a/src/epicker.html b/src/epicker.html index a1008e1a7..35c45982f 100644 --- a/src/epicker.html +++ b/src/epicker.html @@ -83,6 +83,7 @@ html#ublock0-epicker, padding: 2px !important; resize: none !important; width: 100% !important; + word-break: break-all !important; } #ublock0-epicker #resultsetCount { background-color: #aaa !important; diff --git a/src/js/scriptlets/element-picker.js b/src/js/scriptlets/element-picker.js index 8e6593303..8b32e7f41 100644 --- a/src/js/scriptlets/element-picker.js +++ b/src/js/scriptlets/element-picker.js @@ -272,104 +272,118 @@ var highlightElements = function(elems, force) { /******************************************************************************/ +// TODO: Investigate why diff'ing these two strings returns an incorrect result: +// | +// /articles/5c1a7aae1854f30006cb26f7/lede/1545239527833-shutterstock_726017572-copy.jpeg?crop=0.8889xw%3A0.9988xh%3B0.1089xw%2C0xh&resize=650%3A*&output-quality=55 +// /articles/5c1aaea91854f30006cb2f1e/lede/1545253629235-shutterstock_1063990172-copy.jpeg?crop=0.7749xw%3A1xh%3B0.0391xw%2C0xh&resize=650%3A*&output-quality=55 +// | +// This appears to be an issue in the differ, need to investigate it. + +const mergeStrings = function(urls) { + if ( urls.length === 0 ) { return ''; } + if ( + urls.length === 1 || + self.diff_match_patch instanceof Function === false + ) { + return urls[0]; + } + const differ = new self.diff_match_patch(); + let merged = urls[0]; + for ( let i = 1; i < urls.length; i++ ) { + // The differ works at line granularity: we insert a linefeed after + // each character to trick the differ to work at character granularity. + const diffs = differ.diff_main( + //urls[i].replace(/.(?=.)/g, '$&\n'), + //merged.replace(/.(?=.)/g, '$&\n') + urls[i].split('').join('\n'), + merged.split('').join('\n') + ); + const result = []; + for ( const diff of diffs ) { + if ( diff[0] !== 0 ) { + result.push('*'); + } else { + result.push(diff[1].charAt(0)); + } + } + // 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'); + } + return merged; +}; + +/******************************************************************************/ + // https://github.com/gorhill/uBlock/issues/1897 // Ignore `data:` URI, they can't be handled by an HTTP observer. -var backgroundImageURLFromElement = function(elem) { - var style = window.getComputedStyle(elem), - bgImg = style.backgroundImage || '', - matches = /^url\((["']?)([^"']+)\1\)$/.exec(bgImg), - url = matches !== null && matches.length === 3 ? matches[2] : ''; +const backgroundImageURLFromElement = function(elem) { + const style = window.getComputedStyle(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) : ''; }; /******************************************************************************/ // https://github.com/gorhill/uBlock/issues/1725#issuecomment-226479197 -// Limit returned string to 1024 characters. -// Also, return only URLs which will be seen by an HTTP observer. +// Limit returned string to 1024 characters. +// Also, return only URLs which will be seen by an HTTP observer. -var resourceURLFromElement = function(elem) { - var tagName = elem.localName, s; - if ( - (s = netFilter1stSources[tagName]) || - (s = netFilter2ndSources[tagName]) - ) { - s = elem[s]; - if ( typeof s === 'string' && /^https?:\/\//.test(s) ) { - return s.slice(0, 1024); +const resourceURLFromElement = function(elem) { + 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+/) ) { + 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; } return backgroundImageURLFromElement(elem); }; /******************************************************************************/ -var netFilterFromUnion = (function() { - var reTokenizer = /[^0-9a-z%*]+|[0-9a-z%]+|\*/gi; - var a = document.createElement('a'); +const netFilterFromUnion = function(toMergeURL, out) { + const parsedURL = new URL(toMergeURL, document.baseURI); - return function(to, out) { - a.href= to; - to = a.pathname + a.search; - var from = lastNetFilterUnion; + toMergeURL = parsedURL.pathname + parsedURL.search; - // Reset reference filter when dealing with unrelated URLs - if ( from === '' || a.host === '' || a.host !== lastNetFilterHostname ) { - lastNetFilterHostname = a.host; - lastNetFilterUnion = to; - vAPI.messaging.send( - 'elementPicker', - { - what: 'elementPickerEprom', - lastNetFilterSession: lastNetFilterSession, - lastNetFilterHostname: lastNetFilterHostname, - lastNetFilterUnion: lastNetFilterUnion - } - ); - return; - } - - // Related URLs - lastNetFilterHostname = a.host; - - var fromTokens = from.match(reTokenizer); - var toTokens = to.match(reTokenizer); - var toCount = toTokens.length, toIndex = 0; - var fromToken, pos; - - for ( var fromIndex = 0; fromIndex < fromTokens.length; fromIndex += 1 ) { - fromToken = fromTokens[fromIndex]; - if ( fromToken === '*' ) { - continue; - } - pos = toTokens.indexOf(fromToken, toIndex); - if ( pos === -1 ) { - fromTokens[fromIndex] = '*'; - continue; - } - if ( pos !== toIndex ) { - fromTokens.splice(fromIndex, 0, '*'); - fromIndex += 1; - } - toIndex = pos + 1; - if ( toIndex === toCount ) { - fromTokens = fromTokens.slice(0, fromIndex + 1); - break; - } - } - from = fromTokens.join('').replace(/\*\*+/g, '*'); - if ( from !== '/*' && from !== to ) { - var filter = '||' + lastNetFilterHostname + from; - if ( out.indexOf(filter) === -1 ) { - out.push(filter); - } - } else { - from = to; - } - lastNetFilterUnion = from; - - // Remember across element picker sessions + // Reset reference filter when dealing with unrelated URLs + if ( + lastNetFilterUnion === '' || + parsedURL.host === '' || + parsedURL.host !== lastNetFilterHostname + ) { + lastNetFilterHostname = parsedURL.host; + lastNetFilterUnion = toMergeURL; vAPI.messaging.send( 'elementPicker', { @@ -379,14 +393,40 @@ var netFilterFromUnion = (function() { lastNetFilterUnion: lastNetFilterUnion } ); - }; -})(); + return; + } + + // Related URLs + lastNetFilterHostname = parsedURL.host; + + let mergedURL = mergeStrings([ toMergeURL, lastNetFilterUnion ]); + if ( mergedURL !== '/*' && mergedURL !== toMergeURL ) { + const filter = '||' + lastNetFilterHostname + mergedURL; + if ( out.indexOf(filter) === -1 ) { + out.push(filter); + } + } else { + mergedURL = toMergeURL; + } + lastNetFilterUnion = mergedURL; + + // Remember across element picker sessions + vAPI.messaging.send( + 'elementPicker', + { + what: 'elementPickerEprom', + lastNetFilterSession: lastNetFilterSession, + lastNetFilterHostname: lastNetFilterHostname, + lastNetFilterUnion: lastNetFilterUnion + } + ); +}; /******************************************************************************/ // Extract the best possible net filter, i.e. as specific as possible. -var netFilterFromElement = function(elem) { +const netFilterFromElement = function(elem) { if ( elem === null ) { return 0; } if ( elem.nodeType !== 1 ) { return 0; } let src = resourceURLFromElement(elem); @@ -429,7 +469,7 @@ var netFilterFromElement = function(elem) { return candidates.length - len; }; -var netFilter1stSources = { +const netFilter1stSources = { 'audio': 'src', 'embed': 'src', 'iframe': 'src', @@ -438,11 +478,11 @@ var netFilter1stSources = { 'video': 'src' }; -var netFilter2ndSources = { +const netFilter2ndSources = { 'img': 'srcset' }; -var filterTypes = { +const filterTypes = { 'audio': 'media', 'embed': 'object', 'iframe': 'subdocument', @@ -1498,7 +1538,7 @@ var stopPicker = function() { /******************************************************************************/ -var startPicker = function(details) { +const startPicker = function(details) { pickerRoot.addEventListener('load', stopPicker); const frameDoc = pickerRoot.contentDocument; @@ -1611,7 +1651,7 @@ var startPicker = function(details) { /******************************************************************************/ -var bootstrapPicker = function() { +const bootstrapPicker = function() { pickerRoot.removeEventListener('load', bootstrapPicker); vAPI.shutdown.add(stopPicker); vAPI.messaging.send( @@ -1626,7 +1666,7 @@ var bootstrapPicker = function() { pickerRoot = document.createElement('iframe'); pickerRoot.id = vAPI.sessionId; -var pickerCSSStyle = [ +const pickerCSSStyle = [ 'background: transparent', 'border: 0', 'border-radius: 0', @@ -1649,12 +1689,12 @@ var pickerCSSStyle = [ ].join(' !important;'); pickerRoot.style.cssText = pickerCSSStyle; -var pickerCSS1 = [ +const pickerCSS1 = [ '#' + pickerRoot.id + ' {', pickerCSSStyle, '}' ].join('\n'); -var pickerCSS2 = [ +const pickerCSS2 = [ '[' + pickerRoot.id + '-clickblind] {', 'pointer-events: none !important;', '}' diff --git a/src/js/ublock.js b/src/js/ublock.js index d6ae70a0e..758a6c7c1 100644 --- a/src/js/ublock.js +++ b/src/js/ublock.js @@ -407,6 +407,16 @@ var matchBucket = function(url, hostname, bucket, start) { this.epickerTarget = targetElement || ''; this.epickerZap = zap || false; + // https://github.com/uBlockOrigin/uBlock-issues/issues/40 + // The element picker needs this library + vAPI.tabs.injectScript( + tabId, + { + file: '/lib/diff/swatinem_diff.js', + runAt: 'document_end' + } + ); + // https://github.com/uBlockOrigin/uBlock-issues/issues/168 // Force activate the target tab once the element picker has been // injected.