2020-04-29 08:37:43 +00:00
/ * *
* Load the full - size versions of resized images based on their "src"
* attribute , or their containing link ' s "href" attribute . Also , make IFRAMEs
* take up the entire width of their offset parent ( useful for embedded videos
* and whatnot ) . Same goes for the VIDEO elements .
*
* @ title Load full images
* /
( function fullimg ( ) {
/ * C r e a t e a n e w I F R A M E t o g e t a " c l e a n " W i n d o w o b j e c t , s o w e c a n u s e i t s
* console . Sometimes sites ( e . g . Twitter ) override console . log and even
* the entire console object . "delete console.log" or "delete console"
* does not always work , and messing with the prototype seemed more
* brittle than this . * /
var console = ( function ( ) {
var iframe = document . getElementById ( 'xxxJanConsole' ) ;
if ( ! iframe ) {
iframe = document . createElementNS ( 'http://www.w3.org/1999/xhtml' , 'iframe' ) ;
iframe . id = 'xxxJanConsole' ;
iframe . style . display = 'none' ;
( document . body || document . documentElement ) . appendChild ( iframe ) ;
}
return iframe && iframe . contentWindow && iframe . contentWindow . console || {
log : function ( ) { }
} ;
} ) ( ) ;
/ * G e t r i d o f " w i d t h = " , " h e i g h t = " e t c . f o l l o w e d b y n u m b e r s o r n u m b e r p a i r s
* in IMG @ src query strings . * /
var parameterNames = [
'width' ,
'Width' ,
'height' ,
'Height' ,
'maxwidth' ,
'maxWidth' ,
'MaxWidth' ,
'maxheight' ,
'maxHeight' ,
'MaxHeight' ,
'w' ,
'W' ,
'h' ,
'H' ,
'fit' ,
'Fit' ,
'resize' ,
'reSize' ,
'Resize' ,
'size' ,
'Size'
] ;
parameterNames . forEach ( function ( parameterName ) {
var selector = 'img[src*="?' + parameterName + '="]'
+ ', img[src*="?"][src*="&' + parameterName + '="]' ;
/ * M a t c h q u e r y s t r i n g p a r a m e t e r s ( ? [ … & ] n a m e = v a l u e [ & … ] ) w h e r e t h e v a l u e i s
* a number ( e . g . "width=1200" ) or a pair of numbers ( e . g . * "resize=640x480" ) . * /
var parameterReplacementRegexp = new RegExp ( '(\\?[^#]*&)?' + parameterName + '=[1-9][0-9]+(?:(?:[xX,*:]|%2[CcAa]|%3[Aa])[1-9][0-9]+)?([^&#]*)' ) ;
[ ] . forEach . call ( document . querySelectorAll ( selector ) , function ( img ) {
var newSrc = img . src
/* Remove the parameter "name=value" pair from the query string. */
. replace ( parameterReplacementRegexp , '$1$2' )
/* Remove trailing "&" from the query string. */
. replace ( /(\?[^#]*)&(#.*)?$/ , '$1$2' )
/ * R e m o v e e m p t y q u e r y s t r i n g s ( " ? " n o t f o l l o w e d b y
* anything ) from the URL . * /
. replace ( /\?(#.*)?$/ , '$1' )
/* Remove empty fragment identifiers from the URL. */
. replace ( /#$/ , '' )
;
changeSrc ( img , newSrc , 'found image with parameter "' + parameterName + '" in query string' ) ;
} ) ;
} ) ;
/ * S h o w t h e o r i g i n a l i m a g e f o r P o l o p o l y C M S " g e n e r a t e d d e r i v a t i v e s " .
*
* Example :
* https : //sporza.be/polopoly_fs/1.2671026!image/1706320883.jpg_gen/derivatives/landscape670/1706320883.jpg
* https : //sporza.be/polopoly_fs/1.2671026!image/1706320883.jpg
* /
[ ] . forEach . call (
document . querySelectorAll ( 'img[src*="_gen/derivatives/"]' ) ,
function ( img ) {
var matches = img . src . match ( /(.*\.(jpe?g|png|gif))_gen.*\.\2(\?.*)?$/ ) ;
if ( matches && matches [ 1 ] ) {
changeSrc ( img , matches [ 1 ] , 'found image with Polopoly CMS "generated derivative" URL' ) ;
}
}
) ;
/ * T r y t o l o a d t h e o r i g i n a l s f o r i m a g e s w h o s e s o u r c e U R L s l o o k l i k e
* thumbnail / resized versions with dimensions .
* /
[ ] . forEach . call (
document . images ,
function ( img ) {
var oldSrc = img . src ;
/ * E x a m p l e :
* https : //www.cycling-challenge.com/wp-content/uploads/2014/08/IMG_6197-150x150.jpg
* https : //www.cycling-challenge.com/wp-content/uploads/2014/08/IMG_6197.jpg
* /
var matches = oldSrc . match ( /(.*)[-_.@]\d+x\d+(\.[^\/.]+)/ ) ;
if ( matches && matches [ 1 ] && matches [ 2 ] ) {
var newSrc = matches [ 1 ] + matches [ 2 ] ;
return changeSrc ( img , newSrc , 'found image whose URL looks like a thumbnail/resized version' ) ;
}
/ * E x a m p l e :
* https : //upload.wikimedia.org/wikipedia/commons/thumb/8/83/Kowloon-Walled-City-1898.jpg/220px-Kowloon-Walled-City-1898.jpg
* https : //upload.wikimedia.org/wikipedia/commons/8/83/Kowloon-Walled-City-1898.jpg
* /
matches = oldSrc . match ( /(.*\/)thumb\/(.*)\/[^\/]+$/ ) ;
if ( matches ) {
var newSrc = matches [ 1 ] + matches [ 2 ] ;
return changeSrc ( img , newSrc , 'found image whose URL looks like a MediaWiki thumbnail/resized version' ) ;
}
}
) ;
/ * T r y t o l o a d t h e o r i g i n a l s f o r i m a g e s w h o s e s o u r c e U R L s l o o k l i k e
* thumbnail / resized versions with a text label .
*
* Example :
* https : //www.crazyguyonabike.com/pics/docs/00/01/27/84/small/DSCF3555.JPG
* https : //www.crazyguyonabike.com/pics/docs/00/01/27/84/large/DSCF3555.JPG
* /
var thumbnailPathRegexp = /(.*[/.-])(small|thumb|thumbnail|resized|preview|medium)([/.-].*)/ ;
var fullSizePathParts = [
'large' ,
'original' ,
'source' ,
'normal' ,
'xlarge' ,
] ;
[ ] . forEach . call (
document . images ,
function ( img ) {
var oldSrc = img . src ;
var matches = oldSrc . match ( thumbnailPathRegexp ) ;
if ( matches ) {
var newSources = [ ] ;
fullSizePathParts . forEach ( function ( part ) {
newSources . push ( matches [ 1 ] + part + matches [ 3 ] ) ;
} ) ;
changeSrc ( img , newSources , 'found image whose URL looks like a thumbnail/resized version' ) ;
}
}
) ;
/ * C h a n g e t h e I M G @ s r c o f l i n k e d i m a g e s t o t h e i r l i n k ' s A @ h r e f i f t h e y l o o k
* similar , assuming that the linked version is larger . * /
[ ] . forEach . call (
document . querySelectorAll ( 'a img' ) ,
function ( img ) {
if ( ! img . src ) {
return ;
}
var a = img . parentNode ;
while ( a && a . tagName && a . tagName . toLowerCase ( ) !== 'a' ) {
a = a . parentNode ;
}
if ( ! a ) {
return ;
}
var aHref = a . href ;
if ( a . hostname . match ( /\.blogspot\.com$/ ) ) {
/* Get rid of Blogspot's links to useless HTML wrappers. */
aHref = aHref . replace ( /\/(s\d+)-h\/([^\/]+)$/ , '/$1/$2' ) ;
}
if ( aHref === img . src ) {
return ;
}
/* Simplify a URL for similarity calculation. */
function simplifyUrl ( url ) {
return ( '' + url )
. replace ( /\d+/g , '0' )
. replace ( /^https?:/ , '' ) ;
}
var similarity = getSimilarity ( simplifyUrl ( img . src ) , simplifyUrl ( a . href ) ) ;
if ( similarity > 0.66 ) {
changeSrc ( img , aHref , 'found linked image with ' + Math . round ( similarity * 100 ) + '% similarity' ) ;
}
}
) ;
/* Change all Blogspot images that have not been changed yet. */
Array . from (
document . querySelectorAll ( 'img[src*="bp.blogspot.com/"]' )
) . forEach ( img => {
let matches ;
if ( ( matches = img . src . match ( /^(.*\/)s(\d+)(\/[^/]+)$/ ) ) && matches [ 2 ] < 9999 ) {
let newSrc = matches [ 1 ] + 's9999' + matches [ 3 ] ;
changeSrc ( img , newSrc , 'found Blogspot image with restricted size (' + matches [ 2 ] + ')' ) ;
}
} ) ;
/* Use larger YouTube thumbnails. */
Array . from (
document . querySelectorAll ( 'img[src*="//yt"][src*=".ggpht.com"]' )
) . forEach ( img => {
let matches ;
if ( ( matches = img . src . match ( /^(.*\/)s(\d+)([^/]+\/photo\.[^/.]+)$/ ) ) && matches [ 2 ] < 1024 ) {
let newSrc = matches [ 1 ] + 's1024' + matches [ 3 ] ;
changeSrc ( img , newSrc , 'found YouTube avatar with restricted size (' + matches [ 2 ] + ')' ) ;
}
} ) ;
/ * G e t r i d o f a l l I M G @ s r c s e t a t t r i b u t e s t h a t h a v e n o t b e e n r e m o v e d i n t h e
* previous steps .
* /
[ ] . forEach . call (
document . querySelectorAll ( 'img[srcset]' ) ,
function ( img ) {
console . log ( 'Load full images: removing srcset attribute: ' , img ) ;
img . originalSrcset = img . getAttribute ( 'srcset' ) ;
img . removeAttribute ( 'srcset' ) ;
}
) ;
/ * M a k e n a t i v e V I D E O e l e m e n t s a n d v i d e o I F R A M E s t a k e u p t h e e n t i r e w i d t h
* of their offset parent . * /
var elementsToEnlargeSelectors = [
'video' ,
'iframe.twitter-tweet-rendered' ,
'iframe[src*="embed"]' ,
'iframe[src*="video"]' ,
'iframe[src*="syndication"]' ,
'iframe[class*="altura"]' ,
'iframe[id*="altura"]' ,
'iframe[src*="altura"]' ,
'iframe[src*="//e.infogr.am/"]' ,
'iframe[src*="//www.kickstarter.com/projects/"]' ,
'iframe[src*="//media-service.vara.nl/player.php"]' ,
'iframe[src*="//player.vimeo.com/video/"]'
] ;
[ ] . forEach . call (
document . querySelectorAll ( elementsToEnlargeSelectors . join ( ', ' ) ) ,
function ( element ) {
var scale = element . offsetParent . offsetWidth / element . offsetWidth ;
var newWidth = Math . round ( element . offsetWidth * scale ) ;
var newHeight = Math . round ( element . offsetHeight * scale ) ;
console . log (
'Load full images: resizing element ' , element ,
' from ' + element . offsetWidth + 'x' + element . offsetHeight
+ ' to ' + newWidth + 'x' + newHeight
) ;
element . xxxJanReadableAllowStyle = true ;
element . style . width = newWidth + 'px' ;
element . style . height = newHeight + 'px' ;
}
) ;
/* Show controls on AUDIO and VIDEO elements. */
[ ] . forEach . call (
document . querySelectorAll ( 'audio, video' ) ,
function ( element ) {
element . controls = true ;
}
) ;
/* Show controls on YouTube embeds. */
[ ] . forEach . call (
document . querySelectorAll ( 'iframe[src^="https://www.youtube.com/embed/"][src*="?"][src*="=0"]' ) ,
function ( iframe ) {
var beforeAndAfterHash = iframe . src . split ( '#' ) ;
var beforeAndAfterQuery = beforeAndAfterHash [ 0 ] . split ( '?' ) ;
var newPrefix = beforeAndAfterQuery [ 0 ] ;
var newQueryString = '' ;
if ( beforeAndAfterQuery . length > 1 ) {
beforeAndAfterQuery . shift ( ) ;
var newQueryParts = beforeAndAfterQuery
. join ( '?' )
. split ( '&' )
. filter ( function ( keyValuePair ) {
return ! keyValuePair . match ( /^(controls|showinfo|rel)=0$/ ) ;
}
) ;
if ( newQueryParts . length ) {
newQueryString = '?' + newQueryParts . join ( '&' ) ;
}
}
var newHash = '' ;
if ( beforeAndAfterHash . length > 1 ) {
beforeAndAfterHash . shift ( ) ;
newHash = '#' + beforeAndAfterHash . join ( '#' ) ;
}
var newSrc = newPrefix + newQueryString + newHash ;
if ( newSrc !== iframe . src ) {
iframe . src = newSrc ;
}
}
) ;
/ * *
* Crudely calculate the similarity between two strings . Taken from
* https : //stackoverflow.com/a/10473855. An alternative would be the
* Levenshtein distance , implemented in JavaScript here :
* https : //andrew.hedges.name/experiments/levenshtein/
* /
function getSimilarity ( strA , strB ) {
var result = 0 ;
var i = Math . min ( strA . length , strB . length ) ;
if ( i === 0 ) {
return ;
}
while ( -- i ) {
if ( strA [ i ] === strB [ i ] ) {
continue ;
}
if ( strA [ i ] . toLowerCase ( ) === strB [ i ] . toLowerCase ( ) ) {
result ++ ;
} else {
result += 4 ;
}
}
return 1 - ( result + 4 * Math . abs ( strA . length - strB . length ) ) / ( 2 * ( strA . length + strB . length ) ) ;
}
/ * *
* Change the IMG @ src and fall back to the original source if the new
* source triggers an error . You can specify an array of new sources that
* will be tried in order . When all of the new sources fail , the original
* source will be used .
* /
function changeSrc ( img , newSrc , reason )
{
var basename = img . src . replace ( /[?#].*/ , '' ) . replace ( /.*?([^\/]*)\/*$/ , '$1' ) ;
console . log ( '[' + basename + '] Load full images: ' + reason + ': ' , img ) ;
if ( img . hasNewSource ) {
console . log ( '[' + basename + '] Image already has a new source: ' , img ) ;
return ;
}
var newSources = Array . isArray ( newSrc )
? newSrc
: [ newSrc ] ;
while ( ( newSrc = newSources . shift ( ) ) ) {
if ( newSrc && img . src !== newSrc ) {
break ;
}
}
if ( ! newSrc ) {
return ;
}
console . log ( '[' + basename + '] → Old img.src: ' + img . src ) ;
console . log ( '[' + basename + '] → Try img.src: ' + newSrc ) ;
/* Save the original source. */
if ( ! img . originalSrc ) {
img . originalSrc = img . src ;
}
if ( ! img . originalNaturalWidth ) {
img . originalNaturalWidth = img . naturalWidth ;
}
if ( ! img . originalNaturalHeight ) {
img . originalNaturalHeight = img . naturalHeight ;
}
/* Save and disable the srcset on the IMG element. */
if ( img . hasAttribute ( 'srcset' ) ) {
img . originalSrcset = img . getAttribute ( 'srcset' ) ;
img . removeAttribute ( 'srcset' ) ;
}
/* Save and disable the srcset in the container PICTURE element's SOURCE descendants. */
if ( img . parentNode . tagName . toLowerCase ( ) === 'picture' ) {
[ ] . forEach . call (
img . parentNode . querySelectorAll ( 'source[srcset]' ) ,
function ( source ) {
source . originalSrcset = source . getAttribute ( 'srcset' ) ;
source . removeAttribute ( 'srcset' ) ;
}
) ;
}
/ * W h e n t h e n e w s o u r c e h a s f a i l e d t o l o a d , l o a d t h e n e x t o n e f r o m t h e
* list of possible new sources . If there are no more left , revert to
* the original source . * /
var errorHandler ;
if ( newSources . length ) {
console . log ( '[' + basename + '] Setting errorHandler to loadNextNewSrc for ' , img , '; newSources: "' + newSources . join ( '", "' ) + '"; reason:' , reason ) ;
errorHandler = function loadNextNewSrc ( ) {
img . removeEventListener ( 'error' , loadNextNewSrc ) ;
changeSrc ( img , newSources , reason ) ;
} ;
} else {
console . log ( '[' + basename + '] Setting errorHandler to restoreOriginalSrc for ' , img , '; originalSrc: "' + img . originalSrc + '"; reason:' , reason ) ;
errorHandler = function restoreOriginalSrc ( ) {
console . log ( '[' + basename + '] Load full images: error while loading new source for image: ' , img ) ;
console . log ( '[' + basename + '] → Unable to load new img.src: ' + newSrc ) ;
console . log ( '[' + basename + '] → Resetting to original img.src: ' + img . originalSrc ) ;
img . removeEventListener ( 'error' , restoreOriginalSrc ) ;
/* Restore the original source. */
img . src = img . originalSrc ;
/* Re-enable the original srcset on the IMG element. */
if ( img . originalSrcset ) {
img . setAttribute ( 'srcset' , img . originalSrcset ) ;
delete img . originalSrcset ;
}
/* Re-enable the original srcset in the container PICTURE element's SOURCE descendants. */
if ( img . parentNode . tagName . toLowerCase ( ) === 'picture' ) {
[ ] . forEach . call (
img . parentNode . querySelectorAll ( 'source' ) ,
function ( source ) {
if ( source . originalSrcset ) {
source . setAttribute ( 'srcset' , source . originalSrcset ) ;
delete source . originalSrcset ;
}
}
) ;
}
} ;
}
img . addEventListener ( 'error' , errorHandler ) ;
/ * W h e n t h e n e w s o u r c e i m a g e i s s m a l l e r t h a n t h e o r i g i n a l i m a g e ,
* treat that as an error , too . * /
img . addEventListener ( 'load' , function ( ) {
if ( img . naturalWidth * img . naturalHeight < img . originalNaturalWidth * img . originalNaturalHeight ) {
console . log ( '[' + basename + '] Load full images: new image (' , img . naturalWidth , 'x' , img . naturalHeight , ') is smaller than old image (' , img . originalNaturalWidth , 'x' , img . originalNaturalHeight , '): ' , img ) ;
return errorHandler ( ) ;
}
if ( img . src !== img . originalSrc ) {
console . log ( '[' + basename + '] → Success: ' + img . src ) ;
img . hasNewSource = true ;
}
} ) ;
/* Finally, actually try to load the image. */
img . src = newSrc ;
}
} ) ( ) ;