Advertisement
Guest User

Mouseover Popup Image Viewer - tampermonkey

a guest
Jun 12th, 2018
1,371
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name        Mouseover Popup Image Viewer
  3. // @namespace   http://w9p.co/userscripts/
  4. // @description Shows images and videos behind links and thumbnails.
  5. // @version     2017.9.29-2
  6. // @author      kuehlschrank
  7. // @homepage    http://w9p.co/userscripts/mpiv/
  8. // @icon        https://w9p.co/userscripts/mpiv/icon.png
  9. // @include     http*
  10. // @grant       GM_getValue
  11. // @grant       GM_setValue
  12. // @grant       GM_xmlhttpRequest
  13. // @grant       GM_openInTab
  14. // @grant       GM_registerMenuCommand
  15. // @grant       GM_setClipboard
  16. // @connect-src *
  17. // ==/UserScript==
  18.  
  19. 'use strict';
  20.  
  21. var d = document, wn = window, hostname = location.hostname, trusted = ['greasyfork.org', 'w9p.co'], imgtab = d.images.length == 1 && d.images[0].parentNode == d.body && !d.links.length, cfg = loadCfg(), enabled = cfg.imgtab || !imgtab, _ = {}, hosts;
  22.  
  23. function loadCfg() {
  24.     return fixCfg(GM_getValue('cfg'), true);
  25. }
  26.  
  27. function fixCfg(s, save) {
  28.     var cfg, def = {
  29.         version: 5,
  30.         delay: 500,
  31.         start: 'auto',
  32.         zoom: 'context',
  33.         center: false,
  34.         imgtab: false,
  35.         close: true,
  36.         preload: false,
  37.         css: '',
  38.         scales: [],
  39.         hosts: '',
  40.         scale: 1.5,
  41.         xhr: true
  42.     };
  43.     try { cfg = JSON.parse(s); } catch(ex) {}
  44.     if(typeof cfg != 'object' || !cfg) cfg = {}; else if(cfg.version == def.version) return cfg;
  45.     for(var dp in def) {
  46.         if(def.hasOwnProperty(dp) && typeof cfg[dp] != typeof def[dp]) cfg[dp] = def[dp];
  47.     }
  48.     if(cfg.version == 3 && cfg.scales[0] === 0) cfg.scales[0] = '0!';
  49.     for(var cp in cfg) {
  50.         if(!def.hasOwnProperty(cp)) delete cfg[cp];
  51.     }
  52.     cfg.version = def.version;
  53.     if(save) saveCfg(cfg);
  54.     return cfg;
  55. }
  56.  
  57. function saveCfg(newCfg) {
  58.     GM.setValue('cfg', JSON.stringify(cfg = newCfg));
  59. }
  60.  
  61. function loadHosts() {
  62.     var hosts = [
  63.         {d:'startpage', r:/\boiu=(.+)/, s:'$1', follow:true},
  64.         {r:/[\/\?=](https?.+?)(&|$)/, s:'$1', follow:true},
  65.         {d:'4chan.org', e:'.is_catalog .thread a[href*="/thread/"], .catalog-thread a[href*="/thread/"]', q:'.op .fileText a', css:'#post-preview{display:none}'},
  66.         {r:/500px\.com\/photo\//, q:'meta[property="og:image"]'},
  67.         {r:/attachment\.php.+attachmentid/},
  68.         {r:/abload\.de\/image/, q:'#image'},
  69.         {d:'amazon.', r:/(https?:\/\/[\.a-z-]+amazon\.com\/images\/I\/.+?)\./, s:function(m) { var uh = d.getElementById('universal-hover'); if(uh) return ''; return m[1] + '.jpg'; }, css:'#zoomWindow{display:none!important;}'},
  70.         {r:/(chronos\.to|coreimg\.net)\/t\/([0-9]+)\/([0-9]+)\/([a-z0-9]+)/, s:'http://i$2.$1/i/$3/$4.jpg'},
  71.         {r:/de?pic\.me\/[0-9a-z]{8,}/, q:'#pic'},
  72.         {r:/deviantart\.com\/art\//, s:function(m, node) { return /\b(film|lit)/.test(node.className) || /in Flash/.test(node.title) ? '' : m.input; }, q:['#download-button[href*=".jpg"], #download-button[href*=".gif"], #download-button[href*=".png"], #gmi-ResViewSizer_fullimg', 'img.dev-content-full']},
  73.         {r:/disqus\.com/, s:''},
  74.         {r:/dropbox\.com\/sh?\/.+\.(jpe?g|gif|png)/i, q:function(text, doc) { var i = qs('img.absolute-center', doc); return i ? i.src.replace(/(size_mode)=\d+/, '$1=5') : false; }},
  75.         {d:'dropbox.com', r:/(.+?&size_mode)=\d+(.*)/, s:'$1=5$2'},
  76.         {r:/ebay\.[^\/]+\/itm\//, q:function(text) { return text.match(/https?:\/\/i\.ebayimg\.com\/[^\.]+\.JPG/i)[0].replace(/~~60_\d+/, '~~60_57'); }},
  77.         {r:/i.ebayimg.com/, s:function(m, node) { if(qs('.zoom_trigger_mask', node.parentNode)) return ''; return m.input.replace(/~~60_\d+/, '~~60_57'); }},
  78.         {r:/fastpic\.ru\/view\//, q:'#picContainer img'},
  79.         {d:'facebook.com', e:'a[href*="ref=hovercard"]', s:function(m, node) { return 'https://www.facebook.com/photo.php?fbid=' + /\/[0-9]+_([0-9]+)_/.exec(qs('img', node).src)[1]; }, follow:true},
  80.         {d:'facebook.com', r:/(fbcdn|fbexternal).*?(app_full_proxy|safe_image).+?(src|url)=(http.+?)[&\"']/, s:function(m, node) { return contains(node.parentNode.className, 'video') && contains(m[4], 'fbcdn') ? '' : decodeURIComponent(m[4]); }, html:true, follow:true},
  81.         {r:/facebook\.com\/(photo\.php|[^\/]+\/photos\/)/, s:function(m, node) { if(node.id == 'fbPhotoImage') return false; if(/gradient\.png$/.test(m.input)) return ''; return m.input.replace('www.facebook.com', 'mbasic.facebook.com'); }, q:'div + span > a:first-child:not([href*="tag_faces"]), div + span > a[href*="tag_faces"] ~ a', rect:'#fbProfileCover'},
  82.         {r:/fbcdn.+?[0-9]+_([0-9]+)_[0-9]+_[a-z]\.(jpg|png)/, s:function(m) { try { if(/[\.^]facebook\.com$/.test(hostname)) return unsafeWindow.PhotoSnowlift.getInstance().stream.cache.image[m[1]].url; } catch(ex) {} return false; }, manual:true},
  83.         {r:/(https?:\/\/(fbcdn-[\w\.\-]+akamaihd|[\w\.\-]+?fbcdn)\.net\/[\w\/\.\-]+?)_[a-z]\.(jpg|png)(\?[0-9a-zA-Z0-9=_&]+)?/, s:function(m, node) { if(node.id == 'fbPhotoImage') { var a = qs('a.fbPhotosPhotoActionsItem[href$="dl=1"]', d.body); if(a) { return contains(a.href, m.input.match(/[0-9]+_[0-9]+_[0-9]+/)[0]) ? '' : a.href; } } if(m[4]) return false; if(contains(node.parentNode.outerHTML, '/hovercard/')) return ''; var gp = node.parentNode.parentNode; if(contains(node.outerHTML, 'profile') && contains(gp.href, '/photo')) return false; return m[1].replace(/\/[spc][\d\.x]+/g, '').replace('/v/', '/') + '_n.' + m[3]; }, rect:'.photoWrap'},
  84.         {r:/firepic\.org\/\?v=/, q:'.well img[src*="firepic.org"]'},
  85.         {r:/flickr\.com\/photos\/([0-9]+@N[0-9]+|[a-z0-9_\-]+)\/([0-9]+)/, s:function(m) { return m.input.indexOf('/sizes/') < 0 ? 'https://www.flickr.com/photos/' + m[1] + '/' + m[2] + '/sizes/sq/' : false; }, q:function(text, doc) { var links = qsa('.sizes-list a', doc); return 'https://www.flickr.com' + links[links.length-1].getAttribute('href'); }, follow:true},
  86.         {r:/flickr\.com\/photos\/.+\/sizes\//, q:'#allsizes-photo > img'},
  87.         {r:/gallery(nova|sense)\.se\/site\/v\//, q:'a[href*="/upload/"]'},
  88.         {r:/gifbin\.com\/.+\.gif$/, xhr:true},
  89.         {r:/(gfycat\.com\/)(gifs\/detail\/|iframe\/)?([a-z]+)/i, s:'https://$1$3', q:['meta[content$=".webm"]', '#webmsource', 'source[src$=".webm"]']},
  90.         {r:/googleusercontent\.com\/(proxy|gadgets\/proxy.+?(http.+?)&)/, s:function(m) { return m[2] ? decodeURIComponent(m[2]) : m.input.replace(/w\d+-h\d+($|-p)/, 'w0-h0'); }},
  91.         {r:/(googleusercontent|ggpht)\.com\//, s:function(m, node) { if(contains(m.input, 'webcache.') || node.outerHTML.match(/favicons\?|\b(Ol Rf Ep|Ol Zb ag|Zb HPb|Zb Gtb|Rf Pg|ho PQc|Uk wi hE|go wi Wh|we D0b|Bea)\b/) || matches(node, '.g-hovercard *, a[href*="profile_redirector"] > img')) return ''; return m.input.replace(/\/s\d{2,}-[^\/]+|\/w\d+-h\d+/, '/s0').replace(/=[^\/]+$/, ''); }},
  92.         {r:/heberger-image\.fr\/images/, q:'#myimg'},
  93.         {r:/hostingkartinok\.com\/show-image\.php.*/, q:'.image img'},
  94.         {r:/imagearn\.com\/image/, q:'#img', xhr:true},
  95.         {r:/imagefap\.com\/(image|photo)/, q:function(text, doc) { return qs('*[itemprop="contentUrl"]', doc).textContent; }},
  96.         {r:/imagebam\.com\/image\//, q:'img[id]', tabfix:true, xhr:contains(hostname, 'planetsuzy')},
  97.         {r:/imageban\.(ru|net)\/show|imgnova\.com|cweb-pix\.com|(imagebunk|imagewaste)\.com\/(image|pictures\/[0-9]+)/, q:'#img_obj', xhr:true},
  98.         {r:/(imagepdb\.com|imgsure\.com|www\.pixoverflow\.com|imgwiki\.org|freeimgup\.com\/xxx)\/\?v=([0-9]+$|.+(?=\.[a-z]+))/, s:'http://$1/images/$2.jpg', xhr:true},
  99.         {r:/img(\d+)\.(imageshack\.us)\/img\\1\/\d+\/(.+?)\.th(.+)$/, s:'https://$2/download/$1/$3$4'},
  100.         {r:/imageshack\.us\/i\//, q:'#share-dl'},
  101.         {r:/imageshost\.ru\/photo\//i, q:'#bphoto'},
  102.         {r:/imageteam\.org\/img/, q:'img[alt="image"]'},
  103.         {r:/(imagetwist\.com|imageshimage\.com|imgflare\.com|imgearn\.net)\/[a-z0-9]{8,}/, q:'img.pic', xhr:true},
  104.         {r:/imageupper\.com\/i\//, q:'#img', xhr:true},
  105.         {r:/imagepix\.org\/image\/(.+)\.html$/, s:'http://imagepix.org/full/$1.jpg', xhr:true},
  106.         {r:/imageporter\.com\/i\//, s:'/_t//', xhr:true},
  107.         {r:/imagevenue\.com\/img\.php/, q:'#thepic'},
  108.         {r:/imagezilla\.net\/show\//, q:'#photo', xhr:true},
  109.         {r:/(images-na\.ssl-images-amazon.com|media-imdb\.com)\/images\/.+?\.jpg/, s:'/V1\\.?_.+?\\.//g', distinct:true},
  110.         {r:/imgbox\.com\/([a-z0-9]+)$/i, q:'#img', xhr:hostname != 'imgbox.com'},
  111.         {r:/imgchili\.(net|com)\/show/, q:'#show_image', xhr:true},
  112.         {r:/(hosturimage\.com|imageontime\.org|imggoo\.com|imgwel\.com|imageboom\.net|imageon\.org|img4ever\.net|imgcandy\.net|imgcredit\.xyz|imgdevil\.com|imgrun\.net|imgtrial\.com|imgult\.com|img\.yt|picspornfree\.me|pixliv\.com|pixxx\.me|uplimg\.com|xxxscreens\.com|xxxupload\.org)\/img-|imgbb\.net\/v-/, s:function(m) { return m.input.replace(/\/(v-[0-9a-f]+)_.+/, '$1').replace('http://img.yt', 'https://img.yt'); }, q:['img.centred_resized, #image', 'img[src*="/upload/big/"]'], xhr:true, post:'imgContinue=Continue%20to%20image%20...%20'},
  113.         {r:/(foxyimg\.link|imgclick\.net|imgdragon\.com|imgmaid\.net|imgpaying\.com|imageeer\.com|imgdiamond\.com|imgmega\.com|imgsee\.me|imgtrex\.com|imgtiger\.org|pic-maniac\.com|picexposed\.com)\/([a-z0-9]+)/,  q:'img.pic', xhr:true, post:function(m) { return 'op=view&id=' + m[2] + '&pre=1&submit=Continue%20to%20image...'; }},
  114.         {r:/imgflip\.com\/(i|gif)\/([^\/?#]+)/, s:function(m) { return 'https://i.imgflip.com/' + m[2] + (m[1] == 'i' ? '.jpg' : '.mp4'); }},
  115.         {r:/imgsen\.se\/upload\//, s:'/small/big/', xhr:!true},
  116.         {r:/imgtheif\.com\/image\//, q:'a > img[src*="/pictures/"]'},
  117.         {r:/imgur\.com\/(a|gallery|t\/[a-z0-9_-]+)\/([a-z0-9]+)(#[a-z0-9]+)?/i, s:function(m) { return 'https://imgur.com/' + m[1] + '/' + m[2] + '' + (m[3] || ''); }, g:function(text, url, cb) { var mk = function(o, imgs) { var items = []; if(!o || !imgs) return items; for(var i = 0, len = imgs.length, cur; i < len && (cur = imgs[i]); i++) { var iu = 'https://i.imgur.com/' + cur.hash + cur.ext; if(cur.ext == '.gif' && !(cur.animated === false)) iu = [iu.replace('.gif', '.webm'), iu.replace('.gif', '.mp4'), iu]; items.push({url:iu, desc:cur.title && cur.description ? cur.title + ' - ' + cur.description : (cur.title || cur.description)}); } if(o.is_album && !contains(items[0].desc, o.title)) items.title = o.title; return items; }, m = /(mergeConfig\('gallery',\s*|Imgur\.Album\.getInstance\()(\{[\s\S]+?\})\);/.exec(text), o1 = eval('(' + m[2].replace(/analytics\s*:\s*analytics/, 'analytics:null').replace(/decodeURIComponent\(.+?\)/, 'null') + ')'), o = o1.image || o1.album, imgs = o.is_album ? o.album_images.images : [o]; if(!o.num_images || o.num_images <= imgs.length) return mk(o, imgs); GM.xmlhttpRequest({method:'GET',url:'https://imgur.com/ajaxalbums/getimages/' + o.hash + '/hit.json?all=true',onload:function(res) { var imgs; try { imgs = JSON.parse(res.responseText).data.images; } catch(ex) {} cb(mk(o, imgs)); }}); }, css:'.post > .hover { display:none!important; }'},
  118.         {r:/imgur\.com\/.+,/i, g:function(text, url) { var hn = /([a-z]{2,}\.)?imgur\.com/.exec(url)[0]; return /.+\/([a-z0-9,]+)/i.exec(url)[1].split(',').map(function(id) { return {url:'https://i.' + hn + '/' + id + '.jpg'}; }); }},
  119.         {r:/([a-z]{2,}\.)?imgur\.com\/(r\/[a-z]+\/|[a-z0-9]+#)?([a-z0-9]{5,})($|\?|\.([a-z]+))/i, s:function(m, node) { if(/memegen|random|register|search|signin/.test(m.input)) return ''; if(/(i\.([a-z]+\.)?)?imgur\.com\/(a\/|gallery\/)?/.test(node.parentNode.href || node.parentNode.parentNode.href)) return false; var url = 'https://i.' + (m[1] || '').replace('www.', '') + 'imgur.com/' + m[3].replace(/(.{7})[bhm]$/, '$1') + '.' + (m[5] ? m[5].replace(/gifv?/, 'webm') : 'jpg'); return contains(url, '.webm') ? [url, url.replace('.webm', '.mp4'), url.replace('.webm', '.gif')] : url; }},
  120.         {d:'instagram.com', e:['a[href*="/p/"]', 'a[role="button"][data-reactid*="scontent-"]', 'article div', 'article div div img'], s:function(m, node) { var n = closest(node, 'a[href*="/p/"], article'); if(!n) return false; var a = matches(n, 'a[href*="/p/"]') ? n : qs('a[href*="/p/"]', n); return a.href; }, follow:true},
  121.         {r:/instagr(\.am|am\.com)\/p\//i, s:function(m) { return m.input.substr(0, m.input.lastIndexOf('/')) + '/?__a=1'; }, q:function(text) { var m = JSON.parse(text).graphql.shortcode_media;return m.video_url || m.display_url.replace(/\/[sp]\d+x\d+\//, '/').replace(/\?.+/, ''); }, rect:'div.PhotoGridMediaItem', c:function(text) { var m = JSON.parse(text).graphql.shortcode_media.edge_media_to_caption.edges[0]; if ( m === undefined ) { return "(no caption)"; } return m.node.text; } },
  122.         {r:/(istoreimg\.com\/i|itmages\.ru\/image\/view)\//, q:'#image'},
  123.         {d:'kat.cr', r:/confirm\/url\/([^\/]+)/, s:function(m) { return wn.atob(decodeURIComponent(m[1])); }, follow:true},
  124.         {r:/(lazygirls\.info\/.+_.+?\/[a-z0-9_]+)($|\?)/i, s:'http://www.$1?display=fullsize', q:'img.photo', xhr:hostname != 'www.lazygirls.info'},
  125.         {r:/ld-host\.de\/show/, q:'#image'},
  126.         {r:/(listal|lisimg)\.com\/(view)?image\/([0-9]+)/, s:'http://iv1.lisimg.com/image/$3/0full.jpg'},
  127.         {r:/(livememe\.com|lvme\.me)\/([^\.]+)$/, s:'http://i.lvme.me/$2.jpg'},
  128.         {r:/lostpic\.net\/\?(photo|view)/, q:['#cool > img', '.casem img']},
  129.         {r:/makeameme\.org\/meme\/([^\/?#]+)/, s:'https://media.makeameme.org/created/$1.jpg'},
  130.         {r:/modelmayhem\.com\/photos\//, s:'/_m//'},
  131.         {r:/modelmayhem\.com\/avatars\//, s:'/_t/_m/'},
  132.         {r:/(min\.us|minus\.com)\/(i\/|l)([a-z0-9]+)$/i, s:'https://i.minus.com/i$3.jpg'},
  133.         {r:/(min\.us|minus\.com)\/m[a-z0-9]+$/i, g:function(text) { var m = /gallerydata = ({[\w\W]+?});/.exec(text), o = JSON.parse(m[1]), items = []; items.title = o.name; for(var i = 0, len = o.items.length, cur; i < len && (cur = o.items[i]); i++) { items.push({url:'https://i.minus.com/i' + cur.id + '.jpg', desc:cur.caption}); } return items; }},
  134.         {r:/(panoramio\.com\/.*?photo(\/|_id=)|google\.com\/mw-panoramio\/photos\/[a-z]+\/)(\d+)/, s:'http://static.panoramio.com/photos/original/$3.jpg'},
  135.         {r:/(\d+\.photobucket\.com\/.+\/)(\?[a-z=&]+=)?(.+\.(jpe?g|png|gif))/, s:'http://i$1$3', xhr:!contains(hostname, 'photobucket.com')},
  136.         {r:/(photosex\.biz|posteram\.ru)\/.+?id=/i, q:'img[src*="/pic_b/"]', xhr:true},
  137.         {r:/pic4all\.eu\/(images\/|view\.php\?filename=)(.+)/, s:'http://$1/images/$3'},
  138.         {r:/piccy\.info\/view3\/(.*)\//, s:'http://piccy.info/view3/$1/orig/', q:'#mainim'},
  139.         {r:/picsee\.net\/([\d\-]+)\/(.+?)\.html/,s:'http://picsee.net/upload/$1/$2'},
  140.         {r:/picturescream\.com\/\?v=/, q:'#imagen img'},
  141.         {r:/(picturescream\.[a-z\/]+|imagescream\.com\/img)\/(soft|x)/, q:'a > img[src*="/images/"]'},
  142.         {r:/pimpandhost\.com\/image\/([0-9]+)/, s:'http://pimpandhost.com/image/$1?size=original', q:'img.original'},
  143.         {r:/pixhost\.org\/show\//, q:'#image', xhr:true},
  144.         {r:/pixhub\.eu\/images/, q:'.image-show img', xhr:true},
  145.         {r:/(pixroute|imgspice)\.com\/.+\.html$/, q:'img[id]', xhr:true},
  146.         {r:/(pixsor\.com|euro-pic\.eu)\/share-([a-z0-9_]+)/i, s:'http://www.$1/image.php?id=$2', xhr:true},
  147.         {r:/postima?ge?\.org\/image\/\w+/, q:['a[href*="dl="]', '#main-image']},
  148.         {r:/radikal\.ru\/(fp|.+\.html)/, q:function(text) { return text.match(/http:\/\/[a-z0-9]+\.radikal\.ru[a-z0-9\/]+\.(jpg|gif|png)/i)[0]; }},
  149.         {d:'reddit.com', r:/i\.reddituploads\.com/},
  150.         {r:/screenlist\.ru\/details/, q:'#picture'},
  151.         {r:/sharenxs\.com\/.+original$/, q:'img.view_photo', xhr:true},
  152.         {r:/sharenxs\.com\/(gallery|view)\//, q:'a[href$="original"]', follow:true},
  153.         {r:/stooorage\.com\/show\//, q:'#page_body div div img', xhr:true},
  154.         {r:/((awsmpic|damimage|imagedecode|imghit|ocaload|swoopic)\.com|(imgflash|imgproof|imgserve|imgget)\.net|(dragimage|gogoimage|imgspot|imgstudio|madimage)\.org|imgs\.it|image\.re)\/img-/, q:'img.centred_resized, img.centred', xhr:true},
  155.         {r:/turboimagehost\.com\/p\//, q:'#imageid', xhr:true},
  156.         {r:/twimg.+\/profile_images/i, s:'/_(reasonably_small|normal|bigger|\d+x\d+)\\././g'},
  157.         {r:/([a-z0-9-]+\.twimg\.com\/media\/[a-z0-9_-]+\.(jpe?g|png|gif))/i, s:'https://$1:orig', rect:'div.tweet a.twitter-timeline-link, div.TwitterPhoto-media'},
  158.         {d:'tumblr.com',  e:'div.photo_stage_img, div.photo_stage > canvas', s:function(m, node) { return /http[^"]+/.exec(node.style.cssText + node.getAttribute('data-img-src'))[0]; }, follow:true},
  159.         {r:/tumblr\.com.+_500\.jpg/, s:['/_500/_1280/', '']},
  160.         {r:/twimg\.com\/1\/proxy.+?t=(.+?)[&_]/i, s:function(m) { return wn.atob(m[1]).match(/http.+/); }},
  161.         {r:/pic\.twitter\.com\/[a-z0-9]+/i, q:function(text) { return text.match(/https?:\/\/twitter\.com\/[^\/]+\/status\/\d+\/photo\/\d+/i)[0]; }, follow:true},
  162.         {d:'tweetdeck.twitter.com', e:'a.media-item, a.js-media-image-link', s:function(m, node) { return /http[^\)]+/.exec(node.style.backgroundImage)[0]; }, follow:true},
  163.         {r:/twitpic\.com(\/show\/[a-z]+)?\/([a-z0-9]+)($|#)/i, s:'https://twitpic.com/show/large/$2'},
  164.         {r:/twitter\.com\/.+\/status\/.+\/photo\//, q:'.OldMedia img, .media img, video.animated-gif, .AdaptiveMedia-singlePhoto img, .AdaptiveMedia-halfWidthPhoto img, .AdaptiveMedia-twoThirdsWidthPhoto img, .AdaptiveMedia-threeQuartersWidthPhoto img', follow:function(url) { return !/\.mp4$/.test(url); }},
  165.         {d:'twitter.com', e:'.grid-tweet > .media-overlay', s:function(m, node) { return node.previousElementSibling.src; }, follow:true},
  166.         {r:/upix\.me\/files/, s:'/#//'},
  167.         {r:/(vine|seenive)\.com?\/v\//, q:'video source, meta[property="twitter:player:stream"]'},
  168.         {r:/(web\.stagr(\.am|am\.com)|websta\.me)\/p\//i, q:function(text, doc) { var node = findNode(['div.jp-jplayer', 'meta[property="og:image"]'], doc); return findFile(node, _.url).replace(/\/[sp]\d+x\d+\//, '/'); }, rect:'div.PhotoGridMediaItem', c:function(text, doc) { var s = qs('meta[name="description"]', doc).getAttribute('content'); return s.substr(0, s.lastIndexOf(' | ')); } },
  169.         {r:/wiki.+\/(thumb|images)\/.+\.(jpe?g|gif|png|svg)\/(revision\/)?/i, s:'/\\/thumb(?=\\/)|\\/scale-to-width(-[a-z]+)?\\/[0-9]+|\\/revision\\/latest|\\/[^\\/]+$//g', xhr:!contains(hostname, 'wiki')},
  170.         {r:/((xxxhost|tinypix)\.me|(xxxces|imgsin)\.com)\/viewer/, q:['.text_align_center > img', 'img[alt]'], xhr:true},
  171.         {r:/(i[0-9]*\.ytimg\.com\/vi\/[^\/]+)/, s:'https://$1/0.jpg', rect:'.video-list-item'},
  172.         {r:/\/\/([^\/]+)\/viewer\.php\?file=(.+)/, s:'http://$1/images/$2', xhr:true},
  173.         {r:/\/albums.+\/thumb_[^\/]/, s:'/thumb_//'},
  174.         {r:/\/\/[^\/]+[^\?:]+\.(jpe?g?|gif|png|svg|webm)($|\?)/i, distinct:true}
  175.     ];
  176.     if(cfg.hosts) {
  177.         var lines = cfg.hosts.split(/[\r\n]+/);
  178.         for(var i = lines.length, s; i-- && (s = lines[i]);) {
  179.             try {
  180.                 var h = JSON.parse(s);
  181.                 if(h.r) h.r = new RegExp(h.r, 'i');
  182.                 if(h.s && typeof h.s == 'string' && contains(h.s, 'return ')) h.s = new Function('m', 'node', h.s);
  183.                 if(h.q && typeof h.q == 'string' && contains(h.q, 'return ')) h.q = new Function('text', 'doc', 'node', h.q);
  184.                 if(contains(h.c, 'return ')) h.c = new Function('text', 'doc', 'node', h.c);
  185.                 hosts.splice(0, 0, h);
  186.             } catch(ex) {
  187.                 handleError('Host rule invalid: ' + s);
  188.             }
  189.         }
  190.     }
  191.     return hosts.filter(function(h) { return !h.d || contains(hostname, h.d); });
  192. }
  193.  
  194. function onMouseOver(e) {
  195.     if(!enabled || e.shiftKey || _.zoom || !activate(e.target, e.ctrlKey)) return;
  196.     updateMouse(e);
  197.     if(e.ctrlKey) {
  198.         startPopup();
  199.     } else if(cfg.start == 'auto' && !_.manual) {
  200.         if(cfg.preload) {
  201.             _.preloadStart = Date.now();
  202.             startPopup();
  203.             setStatus('preloading', 'add');
  204.         } else {
  205.             _.timeout = wn.setTimeout(startPopup, cfg.delay);
  206.         }
  207.         if(cfg.preload) wn.setTimeout(function() { setStatus('preloading', 'remove'); }, cfg.delay);
  208.     }
  209.     else
  210.         setStatus('ready');
  211. }
  212.  
  213. function onMouseOut(e) {
  214.     if(!e.relatedTarget && !e.shiftKey) deactivate();
  215. }
  216.  
  217. function onMouseMove(e) {
  218.     updateMouse(e);
  219.     if(e.shiftKey) return (_.lazyUnload = true);
  220.     if(!_.zoomed && !_.cr) return deactivate();
  221.     if(_.zoom) {
  222.         placePopup();
  223.         var bx = _.view.width/6, by = _.view.height/6;
  224.         setStatus('edge', _.cx < bx || _.cx > _.view.width - bx || _.cy < by || _.cy > _.view.height - by ? 'add' : 'remove');
  225.     }
  226. }
  227.  
  228. function onMouseDown(e) {
  229.     if(e.which != 3 && !e.shiftKey) deactivate(true);
  230.     else if(e.shiftKey && e.which == 1 && _.popup && _.popup.controls) _.controlled = _.zoomed = true;
  231. }
  232.  
  233. function onMouseScroll(e) {
  234.     var dir = (e.deltaY || -e.wheelDelta) > 0 ? 1 : -1;
  235.     if(_.zoom) {
  236.         drop(e);
  237.         var idx = _.scales.indexOf(_.scale);
  238.         idx -= dir;
  239.         if(idx >= 0 && idx < _.scales.length) _.scale = _.scales[idx];
  240.         if(idx == 0 && cfg.close) {
  241.             if(!_.gItems || _.gItems.length < 2) return deactivate(true);
  242.             _.zoom = false;
  243.             showFileInfo();
  244.         }
  245.         if(_.zooming) _.popup.classList.add('mpiv-zooming');
  246.         placePopup();
  247.         updateTitle();
  248.     } else if(_.gItems && _.gItems.length > 1 && _.popup) {
  249.         drop(e);
  250.         nextGalleryItem(dir);
  251.     } else if(cfg.zoom == 'wheel' && dir < 0 && _.popup) {
  252.         drop(e);
  253.         toggleZoom();
  254.     } else {
  255.         deactivate();
  256.     }
  257. }
  258.  
  259. function onKeyDown(e) {
  260.     if(e.keyCode == 16) {
  261.         setStatus('shift', 'add');
  262.         if(_.popup && 'controls' in _.popup) _.popup.controls = true;
  263.     } else if(e.keyCode == 17 && (cfg.start != 'auto' || _.manual) && !_.popup) {
  264.         startPopup();
  265.     }
  266. }
  267.  
  268. function onKeyUp(e) {
  269.     switch(e.keyCode) {
  270.         case 16:
  271.             setStatus('shift', 'remove');
  272.             if(_.popup.controls) _.popup.controls = false;
  273.             if(_.controlled) return _.controlled = false;
  274.             _.popup && (_.zoomed || !('cr' in _) || _.cr) ? toggleZoom() : deactivate(true);
  275.             break;
  276.         case 17:
  277.             break;
  278.         case 27:
  279.             deactivate(true);
  280.             break;
  281.         case 39:
  282.         case 74:
  283.             drop(e);
  284.             nextGalleryItem(1);
  285.             break;
  286.         case 37:
  287.         case 75:
  288.             drop(e);
  289.             nextGalleryItem(-1);
  290.             break;
  291.         case 68:
  292.             drop(e);
  293.             var name = (_.iurl || _.popup.src).split('/').pop().replace(/[:#\?].*/, '');
  294.             if(!contains(name, '.')) name += '.jpg';
  295.             saveFile(_.popup.src, name, function() { setBar('Could not download ' + name + '.', 'error'); });
  296.             break;
  297.         case 84:
  298.             _.lazyUnload = true;
  299.             if(_.tabfix && !_.xhr && tag(_.popup) == 'IMG' && contains(navigator.userAgent, 'Gecko/'))
  300.                 GM.openInTab('data:text/html;,' + encodeURIComponent('<html><head><style>body{margin:0;padding:0;background:#222}.fit{overflow:hidden}.fit>img{max-width:100vw;max-height:100vh}body>img{margin:auto;position:absolute;left:0;right:0;top:0;bottom:0}</style></head><body class="fit"><img onclick="document.body.classList.toggle(\'fit\')" src="' + _.popup.src + '"></body></html>'));
  301.             else
  302.                 GM.openInTab(_.popup.src);
  303.             deactivate();
  304.             break;
  305.         default:
  306.             deactivate(true);
  307.     }
  308.  
  309. }
  310.  
  311. function saveFile(url, name, onError) {
  312.     var save = function(url) {
  313.         var a = ce('a');
  314.         a.href = url;
  315.         a.download = name;
  316.         a.dispatchEvent(new MouseEvent('click'));
  317.     };
  318.     if(contains(['blob:', 'data:'], url.substr(0, 5))) return save(url);
  319.     GM.xmlhttpRequest({
  320.         method: 'GET',
  321.         url: url,
  322.         responseType: 'blob',
  323.         onload: function(res) {
  324.             try {
  325.                 var ou = wn.URL.createObjectURL(res.response);
  326.                 save(ou);
  327.                 wn.setTimeout(function() { wn.URL.revokeObjectURL(ou); }, 1000);
  328.             } catch(ex) {
  329.                 onError(ex);
  330.             }
  331.         },
  332.         onError: onError
  333.     });
  334. }
  335.  
  336. function onContext(e) {
  337.     if(e.shiftKey) return;
  338.     if(cfg.zoom == 'context' && _.popup && toggleZoom()) return drop(e);
  339.     if((cfg.start == 'context' || (cfg.start == 'auto' && _.manual)) && !_.status && !_.popup) {
  340.         startPopup();
  341.         return drop(e);
  342.     }
  343.     wn.setTimeout(function() { deactivate(true); }, 50);
  344. }
  345.  
  346. function onMessage(e) {
  347.     if(!contains(trusted, e.origin.substr(e.origin.indexOf('//') + 2)) || typeof e.data != 'string' || e.data.indexOf('mpiv-rule ') !== 0) return;
  348.     if(!qs('#mpiv-setup', d)) setup();
  349.     var inp = qs('#mpiv-hosts input:first-of-type', d);
  350.     inp.value = e.data.substr(10).trim();
  351.     inp.dispatchEvent(new Event('input', {bubbles:true}));
  352.     inp.parentNode.scrollTop = 0;
  353.     inp.select();
  354. }
  355.  
  356. function startPopup() {
  357.     setStatus(false);
  358.     _.g ? startGalleryPopup() : startSinglePopup(_.url);
  359. }
  360.  
  361. function startSinglePopup(url) {
  362.     setStatus('loading');
  363.     delete _.iurl;
  364.     if(_.follow && !_.q && !_.s) {
  365.         return findRedirect(_.url, function(url) {
  366.             var info = findInfo(url, _.node, true);
  367.             if(!info || !info.url) throw "Couldn't follow redirection target: " + url;
  368.             restartSinglePopup(info);
  369.         });
  370.     }
  371.     if(!_.q || Array.isArray(_.urls)) {
  372.         if(typeof _.c == 'function') {
  373.             _.caption = _.c(d.documentElement.outerHTML, d, _.node);
  374.         } else if(typeof _.c == 'string') {
  375.             var cnode = findNode(_.c, d);
  376.             _.caption = cnode ? findCaption(cnode) : '';
  377.         }
  378.         _.iurl = url;
  379.         return _.xhr ? downloadImage(url, _.url) : setPopup(url);
  380.     }
  381.     parsePage(url, function(iurl, cap, url) {
  382.         if(!iurl) throw 'File not found.';
  383.         if(typeof cap != 'undefined') _.caption = cap;
  384.         if(_.follow === true || typeof _.follow == 'function' && _.follow(iurl)) {
  385.             var info = findInfo(iurl, _.node, true);
  386.             if(!info || !info.url) throw "Couldn't follow URL: " + iurl;
  387.             return restartSinglePopup(info);
  388.         }
  389.         _.iurl = iurl;
  390.         if(_.xhr) downloadImage(iurl, url); else setPopup(iurl);
  391.     });
  392. }
  393.  
  394. function restartSinglePopup(info) {
  395.     for(var prop in info) _[prop] = info[prop];
  396.     startSinglePopup(_.url);
  397. }
  398.  
  399. function startGalleryPopup() {
  400.     setStatus('loading');
  401.     var startUrl = _.url;
  402.     downloadPage(_.url, function(text, url) {
  403.         try {
  404.             var cb = function(items) {
  405.                 if(!_.url || _.url != startUrl) return;
  406.                 _.gItems = items;
  407.                 if(_.gItems.length == 0) {
  408.                     _.gItems = false;
  409.                     throw 'empty';
  410.                 }
  411.                 _.gIndex = findGalleryPosition(_.url);
  412.                 wn.setTimeout(nextGalleryItem, 0);
  413.             };
  414.             var items = _.g(text, url, cb);
  415.             if(typeof items != 'undefined') cb(items);
  416.         } catch(ex) {
  417.             handleError('Parsing error: ' + ex);
  418.         }
  419.     });
  420. }
  421.  
  422. function findGalleryPosition(gUrl) {
  423.     var dir = 0, sel = gUrl.split('#')[1];
  424.     if(sel) {
  425.         if(/^[0-9]+$/.test(sel)) {
  426.             dir += parseInt(sel);
  427.         } else {
  428.             for(var i = _.gItems.length; i--;) {
  429.                 var url = _.gItems[i].url;
  430.                 if(Array.isArray(url)) url = url[0];
  431.                 var file = url.substr(url.lastIndexOf('/') + 1);
  432.                 if(contains(file, sel)) {
  433.                     dir += i;
  434.                     break;
  435.                 }
  436.             }
  437.         }
  438.     }
  439.     return dir;
  440. }
  441.  
  442. function loadGalleryParser(g) {
  443.     if(typeof g == 'function') return g;
  444.     if(typeof g == 'string') return new Function('text', 'url', 'cb', g);
  445.     return function(text, url) {
  446.         var qE = g.entry, qC = g.caption, qI = g.image, qT = g.title, fix = (typeof g.fix == 'string' ? new Function('s', 'isURL', g.fix) : g.fix) || function(s) { return s.trim(); };
  447.         var doc = createDoc(text), items = [], nodes = qsa(qE || qI, doc);
  448.         if(!Array.isArray(qC)) qC = [qC];
  449.         for(var i = 0, node, len = nodes.length; i < len && (node = nodes[i]); i++) {
  450.             var item = {};
  451.             try {
  452.                 item.url = fix(findFile(qE ? qs(qI, node) : node, url), true);
  453.                 item.desc = qC.reduce(function(prev, q) {
  454.                     var n = qs(q, node);
  455.                     if(!n) {
  456.                         [node.previousElementSibling, node.nextElementSibling].forEach(function(es) {
  457.                             if(es && matches(es, qE) === false) n = matches(es, q) ? es : qs(q, es);
  458.                         });
  459.                     }
  460.                     return n ? (prev ? prev + ' - ' : '') + fix(n.textContent) : prev;
  461.                 }, '');
  462.             } catch(ex) {}
  463.             if(item.url) items.push(item);
  464.         }
  465.         var title = qs(qT, doc);
  466.         if(title) items.title = fix(title.getAttribute('content') || title.textContent);
  467.         return items;
  468.     };
  469. }
  470.  
  471. function nextGalleryItem(dir) {
  472.     if(dir > 0 && (_.gIndex += dir) >= _.gItems.length)
  473.         _.gIndex = 0;
  474.     else if(dir < 0 && (_.gIndex += dir) < 0)
  475.         _.gIndex = _.gItems.length - 1;
  476.     var item = _.gItems[_.gIndex];
  477.     if(Array.isArray(item.url)) {
  478.         _.urls =  item.url.slice(0);
  479.         _.url = _.urls.shift();
  480.     } else {
  481.         delete _.urls;
  482.         _.url = item.url;
  483.     }
  484.     setPopup(false);
  485.     startSinglePopup(_.url);
  486.     showFileInfo();
  487.     preloadNextGalleryItem(dir);
  488. }
  489.  
  490. function preloadNextGalleryItem(dir) {
  491.     var idx = _.gIndex + dir;
  492.     if(_.popup && idx >= 0 && idx < _.gItems.length) {
  493.         var url = _.gItems[idx].url;
  494.         if(Array.isArray(url)) url = url[0];
  495.         on(_.popup, 'load', function() {
  496.             var img = ce('img');
  497.             img.src = url;
  498.         });
  499.     }
  500. }
  501.  
  502. function activate(node, force) {
  503.     if(node == _.popup || node == d.body || node == d.documentElement) return;
  504.     var info = parseNode(node);
  505.     if(!info || !info.url || info.node == _.node) return;
  506.     if(info.distinct && !force) {
  507.         var scale = findScale(info.url, info.node.parentNode);
  508.         if(scale && scale < cfg.scale) return;
  509.     }
  510.     if(_.node) deactivate();
  511.     _ = info;
  512.     _.view = viewRect();
  513.     if(cfg.css || _.css) _.style = addStyle((contains(cfg.css, '{') ? cfg.css : '#mpiv-popup {' + cfg.css + '}') + (_.css ? _.css : ''));
  514.     _.zooming = contains(cfg.css, 'mpiv-zooming');
  515.     [_.node.parentNode, _.node, _.node.firstElementChild].some(function(n) {
  516.             if(n && n.title && n.title != n.textContent && !contains(d.title, n.title) && !/^http\S+$/.test(n.title)) {
  517.                 _.tooltip = {node:n, text:n.title};
  518.                 n.title = '';
  519.                 return true;
  520.             }
  521.         });
  522.     on(d, 'mousemove', onMouseMove);
  523.     on(d, 'mouseout', onMouseOut);
  524.     on(d, 'mousedown', onMouseDown);
  525.     on(d, 'contextmenu', onContext);
  526.     on(d, 'keydown', onKeyDown);
  527.     on(d, 'keyup', onKeyUp);
  528.     on(d, 'onwheel' in d ? 'wheel' : 'mousewheel', onMouseScroll);
  529.     return true;
  530. }
  531.  
  532. function deactivate(wait) {
  533.     wn.clearTimeout(_.timeout);
  534.     if(_.req) try { _.req.abort(); } catch(ex) {}
  535.     if(_.tooltip) _.tooltip.node.title = _.tooltip.text;
  536.     updateTitle(true);
  537.     setStatus(false);
  538.     setPopup(false);
  539.     setBar(false);
  540.     rm(_.style);
  541.     _ = {};
  542.     off(d, 'mousemove', onMouseMove);
  543.     off(d, 'mouseout', onMouseOut);
  544.     off(d, 'mousedown', onMouseDown);
  545.     off(d, 'contextmenu', onContext);
  546.     off(d, 'keydown', onKeyDown);
  547.     off(d, 'keyup', onKeyUp);
  548.     off(d, 'onwheel' in d ? 'wheel' : 'mousewheel', onMouseScroll);
  549.     if(wait) {
  550.         enabled = false;
  551.         wn.setTimeout(function() { enabled = true; }, 200);
  552.     }
  553. }
  554.  
  555. function parseNode(node) {
  556.     var a, img, url, info;
  557.     if(!hosts) { hosts = loadHosts(); GM_registerMenuCommand('Set up Mouseover Popup Image Viewer', setup); }
  558.     if(tag(node) == 'A') {
  559.         a = node;
  560.     } else {
  561.         if(tag(node) == 'IMG') {
  562.             img = node;
  563.             if(img.src.substr(0, 5) != 'data:') url = rel2abs(img.src, location.href);
  564.         }
  565.         info = findInfo(url, node);
  566.         if(info) return info;
  567.         a = tag(node.parentNode) == 'A' ? node.parentNode : (tag(node.parentNode.parentNode) == 'A' ? node.parentNode.parentNode : false);
  568.     }
  569.     if(a) {
  570.         url = a.getAttribute('data-expanded-url') || a.getAttribute('data-full-url') || a.getAttribute('data-url') || a.href;
  571.         if(url.length > 750 || url.substr(0, 5) == 'data:') url = false;
  572.         else if(contains(url, '//t.co/')) url = 'http://' + a.textContent;
  573.         info = findInfo(url, a);
  574.         if(info) return info;
  575.     }
  576.     if(img) return {url:img.src, node:img, rect:rect(img), distinct:true};
  577. }
  578.  
  579. function findInfo(url, node, noHtml, skipHost) {
  580.     for(var i = 0, len = hosts.length, tn = tag(node), h, m, html, urls; i < len && (h = hosts[i]); i++) {
  581.         if(h.e && !matches(node, h.e) || h == skipHost) continue;
  582.         if(h.r) {
  583.             if(h.html && !noHtml && (tn == 'A' || tn == 'IMG' || h.e)) {
  584.                 if(!html) html = node.outerHTML;
  585.                 m = h.r.exec(html);
  586.             } else if(url) {
  587.                 m = h.r.exec(url);
  588.             } else {
  589.                 m = null;
  590.             }
  591.         } else {
  592.             m = url ? /.*/.exec(url) : [];
  593.         }
  594.         if(!m || tn == 'IMG' && !('s' in h)) continue;
  595.         if('s' in h) {
  596.             urls = (Array.isArray(h.s) ? h.s : [h.s]).map(function(s) { if(typeof s == 'string') return decodeURIComponent(replace(s, m)); if(typeof s == 'function') return s(m, node); return s; });
  597.             if(h.q && urls.length > 1) { console.log('Rule discarded. Substitution arrays can\'t be combined with property q.'); continue; }
  598.             if(Array.isArray(urls[0])) urls = urls[0];
  599.             if(urls[0] === false) continue;
  600.             urls = urls.map(function(u) { return u ? decodeURIComponent(u) : u; });
  601.         } else {
  602.             urls = [m.input];
  603.         }
  604.         if((h.follow === true || typeof h.follow == 'function' && h.follow(urls[0])) && !h.q && h.s) return findInfo(urls[0], node, false, h);
  605.         var info = {
  606.             node: node,
  607.             url: urls.shift(),
  608.             urls: urls.length ? urls : false,
  609.             r: h.r,
  610.             q: h.q,
  611.             c: h.c,
  612.             g: h.g ? loadGalleryParser(h.g) : h.g,
  613.             xhr: cfg.xhr && h.xhr,
  614.             tabfix: h.tabfix,
  615.             post: typeof h.post == 'function' ? h.post(m) : h.post,
  616.             follow: h.follow,
  617.             css: h.css,
  618.             manual: h.manual,
  619.             distinct: h.distinct,
  620.             rect: rect(node, h.rect)
  621.         };
  622.         if(contains(hostname, 'twitter.com') && !/(facebook|google|twimg|twitter)\.com\//.test(info.url) || hostname == 'github.com' && !/github/.test(info.url) || contains(hostname, 'facebook.com') && /\bimgur\.com/.test(info.url)) info.xhr = 'data';
  623.         return info;
  624.     }
  625. }
  626.  
  627. function downloadPage(url, cb) {
  628.     var req, opts = {
  629.         method: 'GET',
  630.         url: url,
  631.         onload: function(res) {
  632.             try {
  633.                 if(req != _.req) return;
  634.                 delete _.req;
  635.                 if(res.status > 399) throw 'Server error: ' + res.status;
  636.                 cb(res.responseText, res.finalUrl || url);
  637.             } catch(ex) {
  638.                 handleError(ex);
  639.             }
  640.         },
  641.         onerror: function(res) {
  642.             if(req == _.req) handleError(res);
  643.         }
  644.     };
  645.     if(_.post) {
  646.         opts.method = 'POST';
  647.         opts.data = _.post;
  648.         opts.headers = {'Content-Type':'application/x-www-form-urlencoded','Referer':url};
  649.     }
  650.     _.req = req = GM.xmlhttpRequest(opts);
  651. }
  652.  
  653. function downloadImage(url, referer) {
  654.     var start = Date.now(), bar, req;
  655.     _.req = req = GM.xmlhttpRequest({
  656.         method: 'GET',
  657.         url: url,
  658.         overrideMimeType: 'text/plain; charset=x-user-defined',
  659.         headers: {'Accept':'image/png,image/*;q=0.8,*/*;q=0.5','Referer':referer},
  660.         onprogress: function(e) {
  661.             if(req != _.req) return;
  662.             if(!bar && Date.now() - start > 3000 && e.loaded/e.total < 0.5) bar = true;
  663.             if(bar) setBar(parseInt(e.loaded/e.total * 100) + '% of ' + (e.total/1000000).toFixed(1) + ' MB', 'xhr');
  664.         },
  665.         onload: function(res) {
  666.             try {
  667.                 if(req != _.req) return;
  668.                 delete _.req;
  669.                 setBar(false);
  670.                 if(res.status > 399) throw 'HTTP error ' + res.status;
  671.                 var txt = res.responseText, ui8 = new Uint8Array(txt.length), type;
  672.                 for(var i = txt.length; i--;) {
  673.                     ui8[i] = txt.charCodeAt(i);
  674.                 }
  675.                 if(/Content-Type:\s*(.+)/i.exec(res.responseHeaders) && !contains(RegExp.$1, 'text/plain')) type = RegExp.$1;
  676.                 if(!type) {
  677.                     var ext = /\.([a-z0-9]+?)($|\?|#)/i.exec(url) ? RegExp.$1.toLowerCase() : 'jpg', types = {bmp:'image/bmp', gif:'image/gif', jpe:'image/jpeg', jpeg:'image/jpeg', jpg:'image/jpeg', mp4:'video/mp4', png:'image/png', svg:'image/svg+xml', tif:'image/tiff', tiff:'image/tiff', webm:'video/webm'};
  678.                     type = ext in types ? types[ext] : 'application/octet-stream';
  679.                 }
  680.                 var b = new Blob([ui8.buffer], {type:type});
  681.                 if(wn.URL && _.xhr != 'data') return setPopup(wn.URL.createObjectURL(b));
  682.                 var fr = new FileReader();
  683.                 fr.onload = function() { setPopup(fr.result); };
  684.                 fr.onerror = handleError;
  685.                 fr.readAsDataURL(b);
  686.             } catch(ex) {
  687.                 handleError(ex);
  688.             }
  689.         },
  690.         onerror: function(res) {
  691.             if(req == _.req) handleError(res);
  692.         }
  693.     });
  694. }
  695.  
  696. function findRedirect(url, cb) {
  697.     var req;
  698.     _.req = req = GM.xmlhttpRequest({
  699.         url: url,
  700.         method: 'HEAD',
  701.         headers: {Referer:location.href.replace(location.hash, '')},
  702.         onload: function(res) {
  703.             if(req == _.req) cb(res.finalUrl);
  704.         }
  705.     });
  706. }
  707.  
  708. function parsePage(url, cb) {
  709.     downloadPage(url, function(html, url) {
  710.         var iurl, cap, doc = createDoc(html);
  711.         if(typeof _.q == 'function') {
  712.             iurl = _.q(html, doc, _.node);
  713.             if(Array.isArray(iurl)) {
  714.                 _.urls = iurl.slice(0);
  715.                 iurl = _.urls.shift();
  716.             }
  717.         } else {
  718.             var inode = findNode(_.q, doc);
  719.             iurl = inode ? findFile(inode, url) : false;
  720.         }
  721.         if(typeof _.c == 'function') {
  722.             cap = _.c(html, doc, _.node);
  723.         } else if(typeof _.c == 'string') {
  724.             var cnode = findNode(_.c, doc);
  725.             cap = cnode ? findCaption(cnode) : '';
  726.         }
  727.         cb(iurl, cap, url);
  728.     });
  729. }
  730.  
  731. function findNode(q, doc) {
  732.     var node;
  733.     if(!q) return;
  734.     if(!Array.isArray(q)) q = [q];
  735.     for(var i = 0, len = q.length; i < len; i++) {
  736.         node = qs(q[i], doc);
  737.         if(node) break;
  738.     }
  739.     return node;
  740. }
  741.  
  742. function findFile(n, url) {
  743.     var base = qs('base[href]', n.ownerDocument);
  744.     var path =  n.getAttribute('src') || n.getAttribute('data-m4v') || n.getAttribute('href') || n.getAttribute('content') || /https?:\/\/[.\/a-z0-9_+%\-]+\.(jpe?g|gif|png|svg|webm|mp4)/i.exec(n.outerHTML) && RegExp.lastMatch;
  745.     return path ? rel2abs(path.trim(), base ? base.getAttribute('href') : url) : false;
  746. }
  747.  
  748. function findCaption(n) {
  749.     return n.getAttribute('content') || n.getAttribute('title') || n.textContent;
  750. }
  751.  
  752. function checkProgress(start) {
  753.     if(start === true) {
  754.         if(checkProgress.interval) wn.clearInterval(checkProgress.interval);
  755.         checkProgress.interval = wn.setInterval(checkProgress, 150);
  756.         return;
  757.     }
  758.     var p = _.popup;
  759.     if(!p) return wn.clearInterval(checkProgress.interval);
  760.     if(!updateSize()) return;
  761.     wn.clearInterval(checkProgress.interval);
  762.     if(_.preloadStart) {
  763.         var wait = _.preloadStart + cfg.delay - Date.now();
  764.         if(wait > 0) return _.timeout = wn.setTimeout(checkProgress, wait);
  765.     }
  766.     if(_.urls && _.urls.length && Math.max(_.nheight, _.nwidth) < 130) return handleError({type:'error'});
  767.     setStatus(false);
  768.     p.clientHeight;
  769.     p.className = 'mpiv-show';
  770.     updateSpacing();
  771.     updateScales();
  772.     updateTitle();
  773.     placePopup();
  774.     if(!_.bar) showFileInfo();
  775.     if(_.large = _.nwidth > p.clientWidth + _.mbw || _.nheight > p.clientHeight + _.mbh) setStatus('large');
  776.     if(cfg.imgtab && imgtab || cfg.zoom == 'auto') toggleZoom();
  777. }
  778.  
  779. function updateSize() {
  780.     var p = _.popup;
  781.     _.nheight = p.naturalHeight || p.videoHeight || p.loaded && 800;
  782.     _.nwidth  = p.naturalWidth || p.videoWidth || p.loaded && 1200;
  783.     return !!_.nheight;
  784. }
  785.  
  786. function updateSpacing() {
  787.     var s = wn.getComputedStyle(_.popup);
  788.     _.pw = styleSum(s, ['padding-left', 'padding-right']);
  789.     _.ph = styleSum(s, ['padding-top', 'padding-bottom']);
  790.     _.mbw = styleSum(s, ['margin-left', 'margin-right', 'border-left-width', 'border-right-width']);
  791.     _.mbh = styleSum(s, ['margin-top', 'margin-bottom', 'border-top-width', 'border-bottom-width']);
  792. }
  793.  
  794. function updateScales() {
  795.     var scales = cfg.scales.length ? cfg.scales : ['0!', 0.125, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 5, 8, 16], fit = Math.min(( _.view.width - _.mbw)/_.nwidth, (_.view.height - _.mbh)/_.nheight), cutoff = _.scale = Math.min(1, fit);
  796.     _.scales = [];
  797.     for(var i = scales.length; i--;) {
  798.         var val = parseFloat(scales[i]) || fit, opt = typeof scales[i] == 'string' ? scales[i].slice(-1) : 0;
  799.         if(opt == '!') cutoff = val;
  800.         if(opt == '*') _.zscale = val;
  801.         if(val != _.scale) _.scales.push(val);
  802.     }
  803.     _.scales = _.scales.filter(function(x) { return x >= cutoff; });
  804.     _.scales.sort(function(a, b) { return a - b; });
  805.     _.scales.unshift(_.scale);
  806. }
  807.  
  808. function updateMouse(e) {
  809.     _.cx = e.clientX;
  810.     _.cy = e.clientY;
  811.     var r = _.rect;
  812.     if(r) _.cr = _.cx < r.right + 2 && _.cx > r.left - 2 && _.cy < r.bottom + 2 && _.cy > r.top - 2;
  813. }
  814.  
  815. function showFileInfo() {
  816.     if(_.gItems) {
  817.         var item = _.gItems[_.gIndex];
  818.         var c = _.gItems.length > 1 ? '[' + (_.gIndex + 1) + '/' + _.gItems.length + '] ' : '';
  819.         if(_.gIndex == 0 && _.gItems.title && (!item.desc || !contains(item.desc, _.gItems.title))) c += _.gItems.title + (item.desc ? ' - ' : '');
  820.         if(item.desc) c += item.desc;
  821.         if(c) setBar(c.trim(), 'gallery', true);
  822.     } else if('caption' in _) {
  823.         setBar(_.caption, 'caption');
  824.     } else if(_.tooltip) {
  825.         setBar(_.tooltip.text, 'tooltip');
  826.     }
  827. }
  828.  
  829. function updateTitle(reset) {
  830.     if(reset) {
  831.         if(typeof _.title == 'string') d.title = _.title;
  832.     } else {
  833.         if(typeof _.title != 'string') _.title = d.title;
  834.         d.title = Math.round(_.scale * 100) + '% - ' + _.nwidth + 'x' + _.nheight;
  835.     }
  836. }
  837.  
  838. function placePopup() {
  839.     var p = _.popup;
  840.     if(!p) return;
  841.     var x = null, y = null, w = Math.round(_.scale * _.nwidth), h = Math.round(_.scale * _.nheight), cx = _.cx, cy = _.cy, vw = _.view.width, vh = _.view.height;
  842.     if(!_.zoom && (!_.gItems || _.gItems.length < 2) && !cfg.center) {
  843.         var r = _.rect, rx = (r.left + r.right) / 2, ry = (r.top + r.bottom) / 2;
  844.         if(vw - r.right - 40 > w + _.mbw || w + _.mbw < r.left - 40) {
  845.             if(h + _.mbh < vh - 60) y = Math.min(Math.max(ry - h/2, 30), vh - h - 30);
  846.             x = rx > vw/2 ? r.left - 40 - w : r.right + 40;
  847.         } else if(vh - r.bottom - 40 > h + _.mbh || h + _.mbh < r.top - 40) {
  848.             if(w + _.mbw < vw - 60) x = Math.min(Math.max(rx - w/2, 30), vw - w - 30);
  849.             y = ry > vh/2 ? r.top - 40 - h : r.bottom + 40;
  850.         }
  851.     }
  852.     if(x == null) x = Math.round((vw > w ? vw/2 - w/2 : -1 * Math.min(1, Math.max(0, 5/3*(cx/vw-0.2))) * (w - vw)) - (_.pw + _.mbw)/2);
  853.     if(y == null) y = Math.round((vh > h ? vh/2 - h/2 : -1 * Math.min(1, Math.max(0, 5/3*(cy/vh-0.2))) * (h - vh)) - (_.ph + _.mbh)/2);
  854.     p.style.cssText = 'width:' + w + 'px!important;height:' + h + 'px!important;left:' + x + 'px!important;top:' + y + 'px!important';
  855. }
  856.  
  857. function toggleZoom() {
  858.     var p = _.popup;
  859.     if(!p || !_.scales || _.scales.length < 2) return;
  860.     _.zoom = !_.zoom;
  861.     _.zoomed = true;
  862.     _.scale = _.scales[ _.zoom ? (_.scales.indexOf(_.zscale) > 0 ? _.scales.indexOf(_.zscale) : 1) : 0];
  863.     if(_.zooming) p.classList.add('mpiv-zooming');
  864.     placePopup();
  865.     updateTitle();
  866.     setStatus(_.zoom ? 'zoom' : false);
  867.     if(cfg.zoom != 'auto') setBar(false);
  868.     if(!_.zoom) showFileInfo();
  869.     return _.zoom;
  870. }
  871.  
  872. function handleError(o) {
  873.     var m = [o.message || (o.readyState ? 'Request failed.' : (o.type == 'error' ? 'File can\'t be displayed.' + (qs('div[bgactive*="flashblock"]', d) ? ' Check Flashblock settings.' : '') : o))];
  874.         try {
  875.             if(o.stack) m.push(' @ ' + o.stack.replace(/<?@file:.+?\.js/g, ''));
  876.             if(_.r) m.push('RegExp: ' + _.r);
  877.             if(_.url) m.push('URL: ' + _.url);
  878.             if(_.iurl) m.push('File: ' + _.iurl);
  879.             console.log(m.join('\n'));
  880.         } catch(ex) {}
  881.     if(contains(hostname, 'google') && contains(location.search, 'tbm=isch') && !_.xhr && cfg.xhr) {
  882.         _.xhr = true;
  883.         startSinglePopup(_.url);
  884.     } else if(_.urls && _.urls.length) {
  885.         _.url = _.urls.shift();
  886.         if(!_.url)
  887.             deactivate();
  888.         else
  889.             startSinglePopup(_.url);
  890.     } else if(_.node) {
  891.         setStatus('error');
  892.         setBar(m[0], 'error');
  893.     }
  894. }
  895.  
  896. function setStatus(status, flag) {
  897.     var de = d.documentElement, cn = de.className;
  898.     if(flag == 'remove') {
  899.         cn = cn.replace('mpiv-' + status, '');
  900.     } else {
  901.         if(flag != 'add') cn = cn.replace(/mpiv-[a-z]+/g, '');
  902.         if(status && !contains(cn, 'mpiv-' + status)) cn += ' mpiv-' + status;
  903.     }
  904.     de.className = cn;
  905. }
  906.  
  907. function setPopup(src) {
  908.     var p = _.popup;
  909.     if(p) {
  910.         _.zoom = false;
  911.         off(p, 'error', handleError);
  912.         if(typeof p.pause == 'function') p.pause();
  913.         if(!_.lazyUnload) {
  914.             if(p.src.substr(0, 5) == 'blob:') wn.URL.revokeObjectURL(p.src);
  915.             p.src = '';
  916.         }
  917.         rm(p);
  918.         delete _.popup;
  919.     }
  920.     if(!src) return;
  921.     if(src.substr(0, 5) != 'data:' && /\.(webm|mp4)($|\?)/.test(src) || src.substr(0, 10) == 'data:video') {
  922.         var start = Date.now(), bar;
  923.         var onProgress = function(e) {
  924.             var p = e.target;
  925.             if(!p.duration || !p.buffered.length || Date.now() - start < 2000) return;
  926.             var per = parseInt(p.buffered.end(0)/p.duration * 100);
  927.             if(!bar && per > 0 && per < 50) bar = true;
  928.             if(bar) setBar(per + '% of ' + Math.round(p.duration) + 's', 'xhr');
  929.         };
  930.         p = _.popup = ce('video');
  931.         p.autoplay = true;
  932.         p.loop = true;
  933.         p.volume = 0.5;
  934.         p.controls = false;
  935.         on(p, 'progress', onProgress);
  936.         on(p, 'canplaythrough', function(e) { off(e.target, 'progress', onProgress); if(_.bar && _.bar.classList.contains('mpiv-xhr')) { setBar(false); showFileInfo(); } });
  937.     } else {
  938.         p = _.popup = ce('img');
  939.     }
  940.     p.id = 'mpiv-popup';
  941.     on(p, 'error', handleError);
  942.     on(p, 'load', function() { this.loaded = true; });
  943.     if(_.zooming) on(p, 'transitionend', function(e) { e.target.classList.remove('mpiv-zooming'); });
  944.     _.bar ? d.body.insertBefore(p, _.bar) : d.body.appendChild(p);
  945.     p.src = src;
  946.     p = null;
  947.     checkProgress(true);
  948. }
  949.  
  950. function setBar(label, cn) {
  951.     var b = _.bar;
  952.     if(!label) {
  953.         rm(b);
  954.         delete _.bar;
  955.         return;
  956.     }
  957.     if(!b) {
  958.         b = _.bar = ce('div');
  959.         b.id = 'mpiv-bar';
  960.     }
  961.     b.innerHTML = label;
  962.     if(!b.parentNode) {
  963.         d.body.appendChild(b);
  964.         b.clientHeight;
  965.     }
  966.     b.className = 'mpiv-show mpiv-' + cn;
  967. }
  968.  
  969. function rel2abs(rel, abs) {
  970.     if(rel.substr(0, 5) == 'data:') return rel;
  971.     var re = /^([a-z]+:)\/\//;
  972.     if(re.test(rel))  return rel;
  973.     if(!re.exec(abs)) return;
  974.     if(rel.indexOf('//') === 0) return RegExp.$1 + rel;
  975.     if(rel[0] == '/') return abs.substr(0, abs.indexOf('/', RegExp.lastMatch.length)) + rel;
  976.     return abs.substr(0, abs.lastIndexOf('/')) + '/' + rel;
  977. }
  978.  
  979. function replace(s, m) {
  980.     if(!m) return s;
  981.     if(s.charAt(0) == '/' && s.charAt(1) != '/') {
  982.         var mid = /[^\\]\//.exec(s).index+1;
  983.         var end = s.lastIndexOf('/');
  984.         var re = new RegExp(s.substring(1, mid), s.substr(end+1));
  985.         return m.input.replace(re, s.substring(mid+1, end));
  986.     }
  987.     for(var i = m.length; i--;) {
  988.         s = s.replace('$'+i, m[i]);
  989.     }
  990.     return s;
  991. }
  992.  
  993. function addStyle(css) {
  994.     var s = ce('style');
  995.     s.textContent = css;
  996.     d.head.appendChild(s);
  997.     return s;
  998. }
  999.  
  1000. function styleSum(s, p) {
  1001.     for(var i = p.length, x = 0; i--;) {
  1002.         x += parseInt(s.getPropertyValue([p[i]])) || 0;
  1003.     }
  1004.     return x;
  1005. }
  1006.  
  1007. function findScale(url, parent) {
  1008.     var imgs = qsa('img, video', parent);
  1009.     for(var i = imgs.length, img; i-- && (img = imgs[i]);) {
  1010.         if(img.src != url) continue;
  1011.         var s = Math.max((img.naturalHeight || img.videoHeight)/img.offsetHeight, (img.naturalWidth || img.videoWidth)/img.offsetWidth);
  1012.         if(isFinite(s)) return s;
  1013.     }
  1014. }
  1015.  
  1016. function viewRect() {
  1017.     var node = d.compatMode == 'BackCompat' ? d.body : d.documentElement;
  1018.     return {width:node.clientWidth, height:node.clientHeight};
  1019. }
  1020.  
  1021. function rect(node, q) {
  1022.     var n;
  1023.     if(q) {
  1024.         n = node;
  1025.         while(tag(n = n.parentNode) != 'BODY') {
  1026.             if(matches(n, q)) return n.getBoundingClientRect();
  1027.         }
  1028.     }
  1029.     var nodes = qsa('*', node);
  1030.     for(var i = nodes.length; i-- && (n = nodes[i]);) {
  1031.         if(n.offsetHeight > node.offsetHeight) node = n;
  1032.     }
  1033.     return node.getBoundingClientRect();
  1034. }
  1035.  
  1036. function matches(n, q) {
  1037.     var p = Element.prototype, m = p.matches || p.mozMatchesSelector || p.webkitMatchesSelector || p.oMatchesSelector;
  1038.     if(m) return m.call(n, q);
  1039. }
  1040.  
  1041. function closest(n, q) {
  1042.     while(n) {
  1043.         if(matches(n, q)) return n;
  1044.         n = n.parentNode;
  1045.     }
  1046. }
  1047.  
  1048. function tag(n) {
  1049.     return n.tagName.toUpperCase();
  1050. }
  1051.  
  1052. function createDoc(text) {
  1053.     var doc = d.implementation.createHTMLDocument('MPIV');
  1054.     doc.documentElement.innerHTML = text;
  1055.     return doc;
  1056. }
  1057.  
  1058. function rm(n) {
  1059.     if(n && n.parentNode) n.parentNode.removeChild(n);
  1060. }
  1061.  
  1062. function on(n, e, f) {
  1063.     n.addEventListener(e, f);
  1064. }
  1065.  
  1066. function off(n, e, f) {
  1067.     n.removeEventListener(e, f);
  1068. }
  1069.  
  1070. function drop(e) {
  1071.     e.preventDefault();
  1072.     e.stopPropagation();
  1073. }
  1074.  
  1075. function ce(s) {
  1076.     return d.createElement(s);
  1077. }
  1078.  
  1079. function qs(s, n) {
  1080.     return n.querySelector(s);
  1081. }
  1082.  
  1083. function qsa(s, n) {
  1084.     return n.querySelectorAll(s);
  1085. }
  1086.  
  1087. function contains(a, b) {
  1088.     return a && a.indexOf(b) > -1;
  1089. }
  1090.  
  1091. function setup() {
  1092.     var $ = function(s) { return d.getElementById('mpiv-'+s); };
  1093.     var close = function() { rm($('setup')); if(!contains(trusted, hostname)) off(wn, 'message', onMessage); };
  1094.     var update = function() { $('delay').parentNode.style.display = $('preload').parentNode.style.display = $('start-auto').selected ? '' : 'none'; };
  1095.     var check = function(e) {
  1096.         var t = e.target, ok;
  1097.         try {
  1098.             var pes = t.previousElementSibling;
  1099.             if(t.value) {
  1100.                 if(!pes) { var inp = t.cloneNode(); inp.value = ''; t.parentNode.insertBefore(inp, t); }
  1101.                 new RegExp(JSON.parse(t.value).r);
  1102.             } else if(pes) {
  1103.                 pes.focus();
  1104.                 rm(t);
  1105.             }
  1106.             ok = 1;
  1107.         } catch(ex) {}
  1108.         t.style.backgroundColor = ok ? '' : '#ffaaaa';
  1109.     };
  1110.     var exp = function(e) {
  1111.         drop(e);
  1112.         var s = JSON.stringify(getCfg());
  1113.         if(typeof GM.setClipboard == 'function') {
  1114.             GM.setClipboard(s);
  1115.             wn.alert('Settings copied to clipboard!');
  1116.         } else {
  1117.             wn.alert(s);
  1118.         }
  1119.     };
  1120.     var imp = function(e) {
  1121.         drop(e);
  1122.         var s = wn.prompt('Paste settings:');
  1123.         if(!s) return;
  1124.         init(fixCfg(s));
  1125.     };
  1126.     var install = function(e) {
  1127.         drop(e);
  1128.         e.target.parentNode.innerHTML = '<span>Loading...</span><iframe style="width:100%;height:26px;border:0;margin:0;display:none;" src="//w9p.co/userscripts/mpiv/more_host_rules.html" onload="this.style.display=\'\';this.previousElementSibling.style.display=\'none\';"></iframe>';
  1129.     };
  1130.     var getCfg = function() {
  1131.         var cfg = {};
  1132.         var delay = parseInt($('delay').value);
  1133.         if(!isNaN(delay) && delay >= 0) cfg.delay = delay;
  1134.         var scale = parseFloat($('scale').value.replace(',', '.'));
  1135.         if(!isNaN(scale)) cfg.scale = Math.max(1, scale);
  1136.         cfg.start = $('start-context').selected ? 'context' : ($('start-ctrl').selected ? 'ctrl' : 'auto');
  1137.         cfg.zoom = $('zoom-context').selected ? 'context' : ($('zoom-wheel').selected ? 'wheel' : ($('zoom-shift').selected ? 'shift' : 'auto'));
  1138.         cfg.center = $('center').checked;
  1139.         cfg.imgtab = $('imgtab').checked;
  1140.         cfg.close = $('close').selected;
  1141.         cfg.preload = $('preload').checked;
  1142.         cfg.css = $('css').value.trim();
  1143.         cfg.scales = $('scales').value.trim().split(/[,;]*\s+/).map(function(x) { return x.replace(',', '.'); }).filter(function(x) { return !isNaN(parseFloat(x)); });
  1144.         cfg.xhr = $('xhr').checked;
  1145.         var inps = qsa('input', $('hosts')), lines = [];
  1146.         for(var i = 0; i < inps.length; i++) {
  1147.             var s = inps[i].value.trim();
  1148.             if(s) lines.push(s);
  1149.         }
  1150.         lines.sort();
  1151.         cfg.hosts = lines.join('\n');
  1152.         return fixCfg(JSON.stringify(cfg));
  1153.     };
  1154.     var init = function(cfg) {
  1155.         close();
  1156.         if(!contains(trusted, hostname)) on(wn, 'message', onMessage);
  1157.         addStyle('\
  1158.             #mpiv-setup { position:fixed;z-index:2147483647;top:20px;right:20px;padding:20px 30px;background:#eee;width:640px;border:1px solid black; }\
  1159.             #mpiv-setup * { color:black;text-align:left;min-height:unset;margin:unset;padding:unset;line-height:15px;font-size:12px;font-family:sans-serif;box-shadow:none; }\
  1160.             #mpiv-setup a { color:darkblue!important;text-decoration:underline!important; }\
  1161.             #mpiv-setup ul { margin:10px 0 15px 0;padding:0;list-style:none;background:#eee;border:0; }\
  1162.             #mpiv-setup input, #mpiv-setup select, #mpiv-css { display:inline;border:1px solid gray;padding:2px;background:white; }\
  1163.             #mpiv-css { resize:vertical; height:45px; }\
  1164.             #mpiv-scales { width:130px; }\
  1165.             #mpiv-setup li { margin:0;padding:7px 0;vertical-align:middle;background:#eee;border:0 }\
  1166.             #mpiv-zoom { margin-right: 18px; }\
  1167.             #mpiv-delay, #mpiv-scale { width:36px; }\
  1168.             #mpiv-cursor, #mpiv-imgtab, #mpiv-xhr, #mpiv-preload { margin-left:18px; }\
  1169.             #mpiv-hosts { max-height:150px;overflow-y:auto; padding:2px; margin:4px 0;clear:both; }\
  1170.             #mpiv-hosts input, #mpiv-css { width:98%;margin:3px 0; }\
  1171.             #mpiv-setup button { width:150px;margin:0 10px;text-align:center; }\
  1172.         ');
  1173.         var div = ce('div');
  1174.         div.id = 'mpiv-setup';
  1175.         d.body.appendChild(div);
  1176.         div.innerHTML = '\
  1177.             <div><a href="http://w9p.co/userscripts/mpiv/">Mouseover Popup Image Viewer</a><span style="float:right"><a href="#" id="mpiv-import">Import</a> | <a href="#" id="mpiv-export">Export</a></span></div><ul>\
  1178.             <li>Popup: <select><option id="mpiv-start-auto">automatically</option><option id="mpiv-start-context">right click or ctrl</option><option id="mpiv-start-ctrl">ctrl</option></select> <span>after <input id="mpiv-delay" type="text"/> ms</span> <span><input type="checkbox" id="mpiv-preload"/> Start loading immediately</span></li>\
  1179.             <li>Only show popup over scaled-down image when natural size is <input id="mpiv-scale" type="text"/> times larger</li>\
  1180.             <li><input type="checkbox" id="mpiv-center"/> Always centered <input type="checkbox" id="mpiv-imgtab"/> Run in image tabs <input type="checkbox" id="mpiv-xhr" onclick="return this.checked || confirm(\'Do not disable this unless you spoof the HTTP headers yourself.\')"/> Anti-hotlinking workaround</li>\
  1181.             <li>Zoom: <select id="mpiv-zoom"><option id="mpiv-zoom-context">right click or shift</option><option id="mpiv-zoom-wheel">wheel up or shift</option><option id="mpiv-zoom-shift">shift</option><option id="mpiv-zoom-auto">automatically</option></select> Custom scale factors: <input type="text" id="mpiv-scales" placeholder="e.g. 0 0.5 1* 2"/> <span title="values smaller than non-zoomed size are ignored, 0 = fit to window, 0! = same as 0 but also removes smaller values, asterisk after value marks default zoom factor (e.g. 1*)" style="cursor:help">(?)</span></li>\
  1182.             <li>If zooming out further is not possible, <select><option>stay in zoom mode</option><option id="mpiv-close">close popup</option></select></li>\
  1183.             <li><a href="http://w9p.co/userscripts/mpiv/css.html" target="_blank">Custom CSS:</a><div><textarea id="mpiv-css" spellcheck="false"></textarea></li>\
  1184.             <li><a href="http://w9p.co/userscripts/mpiv/host_rules.html" target="_blank">Custom host rules:</a><input type="text" id="mpiv-search" placeholder="Search" style="display:none;float:right;width:70px;padding:1px 2px;font-size:10px;"/><div id="mpiv-hosts"><input type="text" spellcheck="false"></div></li>\
  1185.             <li><a href="#" id="mpiv-install">Install rule from repository...</a></li>\
  1186.             </ul><div style="text-align:center"><button id="mpiv-ok">OK</button><button id="mpiv-cancel">Cancel</button></div>';
  1187.         if(cfg.hosts) {
  1188.             var parent = $('hosts');
  1189.             var lines = cfg.hosts.split(/[\r\n]+/);
  1190.             for(var i = 0, s; i < lines.length && (s = lines[i]); i++) {
  1191.                 var inp = parent.firstElementChild.cloneNode();
  1192.                 inp.value = s;
  1193.                 parent.appendChild(inp);
  1194.                 check({target:inp});
  1195.             }
  1196.             if(lines.length > 5 || setup.search) {
  1197.                 var se = $('search'), sf = function() {
  1198.                     var inps = qsa('input', $('hosts')), s = se.value.toLowerCase();
  1199.                     setup.search = s;
  1200.                     for(var i = 0; i < inps.length; i++) {
  1201.                         inps[i].style.display = !inps[i].value || contains(inps[i].value.toLowerCase(), s) ? '' : 'none';
  1202.                     }
  1203.                 };
  1204.                 on(se, 'input', sf);
  1205.                 se.value = setup.search || '';
  1206.                 if(se.value) sf();
  1207.                 se.style.display = '';
  1208.             }
  1209.         }
  1210.         on($('start-auto').parentNode, 'change', update);
  1211.         on($('cancel'), 'click', close);
  1212.         on($('export'), 'click', exp);
  1213.         on($('import'), 'click', imp);
  1214.         on($('hosts'), 'input', check);
  1215.         on($('install'), 'click', install);
  1216.         on($('ok'), 'click', function() {
  1217.             saveCfg(getCfg());
  1218.             hosts = loadHosts();
  1219.             close();
  1220.         });
  1221.         $('delay').value = cfg.delay;
  1222.         $('scale').value = cfg.scale;
  1223.         $('center').checked = cfg.center;
  1224.         $('imgtab').checked = cfg.imgtab;
  1225.         $('close').selected = cfg.close;
  1226.         $('preload').checked = cfg.preload;
  1227.         $('css').value = cfg.css;
  1228.         $('scales').value = cfg.scales.join(' ');
  1229.         $('xhr').checked = cfg.xhr;
  1230.         $('zoom-' + cfg.zoom).selected = true;
  1231.         $('start-' + cfg.start).selected = true;
  1232.         update();
  1233.         var free = viewRect().height - div.offsetHeight - 60;
  1234.         $('hosts').style.maxHeight = parseInt($('hosts').offsetHeight + 0.8 * free) + 'px';
  1235.         $('css').style.height = parseInt($('css').offsetHeight + 0.2 * free) + 'px';
  1236.         div = null;
  1237.     };
  1238.     init(loadCfg());
  1239. }
  1240.  
  1241. addStyle('\
  1242.     #mpiv-bar { position:fixed;z-index:2147483647;left:0;right:0;top:0;transform:scaleY(0);-webkit-transform:scaleY(0);transform-origin:top;-webkit-transform-origin:top;transition:transform 500ms ease 1000ms;-webkit-transition:-webkit-transform 500ms ease 1000ms;text-align:center;font-family:sans-serif;font-size:15px;font-weight:bold;background:rgba(0, 0, 0, 0.6);color:white;padding:4px 10px; }\
  1243.     #mpiv-bar.mpiv-show { transform:scaleY(1);-webkit-transform:scaleY(1); }\
  1244.     #mpiv-popup.mpiv-show { display:inline; }\
  1245.     #mpiv-popup { display:none;border:1px solid gray;box-sizing:content-box;background-color:white;position:fixed;z-index:2147483647;margin:0;max-width:none;max-height:none;will-change:display,width,height,left,top;cursor:none; }\
  1246.     .mpiv-loading:not(.mpiv-preloading) * { cursor:wait!important; }\
  1247.     .mpiv-edge #mpiv-popup { cursor:default; }\
  1248.     .mpiv-error * { cursor:not-allowed!important; }\
  1249.     .mpiv-ready *, .mpiv-large * { /*cursor:zoom-in!important; cursor:-webkit-zoom-in!important;*/ }\
  1250.     .mpiv-shift * { cursor:default!important; }');
  1251. on(d, 'mouseover', onMouseOver);
  1252. if(contains(hostname, 'google')) {
  1253.     var node = d.getElementById('main');
  1254.     if(node) on(node, 'mouseover', onMouseOver);
  1255. } else if(contains(trusted, hostname)) {
  1256.     on(wn, 'message', onMessage);
  1257.     on(d, 'click', function(e) {
  1258.         var t = e.target;
  1259.         if(e.which != 1 || !/BLOCKQUOTE|CODE|PRE/.test(tag(t) + tag(t.parentNode)) || !/^\s*\{\s*".+:.+\}\s*$/.test(t.textContent)) return;
  1260.         wn.postMessage('mpiv-rule ' + t.textContent, '*');
  1261.         e.preventDefault();
  1262.     });
  1263. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement