1/*! 2 * viewport-units-buggyfill v0.6.0 3 * @web: https://github.com/rodneyrehm/viewport-units-buggyfill/ 4 * @author: Rodney Rehm - http://rodneyrehm.de/en/ 5 */ 6 7 8(function (root, factory) { 9 'use strict'; 10 if (typeof define === 'function' && define.amd) { 11 // AMD. Register as an anonymous module. 12 define([], factory); 13 } else if (typeof exports === 'object') { 14 // Node. Does not work with strict CommonJS, but 15 // only CommonJS-like enviroments that support module.exports, 16 // like Node. 17 module.exports = factory(); 18 } else { 19 // Browser globals (root is window) 20 root.viewportUnitsBuggyfill = factory(); 21 } 22}(this, function () { 23 'use strict'; 24 /*global document, window, navigator, location, XMLHttpRequest, XDomainRequest, CustomEvent*/ 25 26 var initialized = false; 27 var options; 28 var userAgent = window.navigator.userAgent; 29 var viewportUnitExpression = /([+-]?[0-9.]+)(vh|vw|vmin|vmax)/g; 30 var forEach = [].forEach; 31 var dimensions; 32 var declarations; 33 var styleNode; 34 var isBuggyIE = /MSIE [0-9]\./i.test(userAgent); 35 var isOldIE = /MSIE [0-8]\./i.test(userAgent); 36 var isOperaMini = userAgent.indexOf('Opera Mini') > -1; 37 38 var isMobileSafari = /(iPhone|iPod|iPad).+AppleWebKit/i.test(userAgent) && (function() { 39 // Regexp for iOS-version tested against the following userAgent strings: 40 // Example WebView UserAgents: 41 // * iOS Chrome on iOS8: "Mozilla/5.0 (iPad; CPU OS 8_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/39.0.2171.50 Mobile/12B410 Safari/600.1.4" 42 // * iOS Facebook on iOS7: "Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_1 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Mobile/11D201 [FBAN/FBIOS;FBAV/12.1.0.24.20; FBBV/3214247; FBDV/iPhone6,1;FBMD/iPhone; FBSN/iPhone OS;FBSV/7.1.1; FBSS/2; FBCR/AT&T;FBID/phone;FBLC/en_US;FBOP/5]" 43 // Example Safari UserAgents: 44 // * Safari iOS8: "Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4" 45 // * Safari iOS7: "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A4449d Safari/9537.53" 46 var iOSversion = userAgent.match(/OS (\d)/); 47 // viewport units work fine in mobile Safari and webView on iOS 8+ 48 return iOSversion && iOSversion.length>1 && parseInt(iOSversion[1]) < 10; 49 })(); 50 51 var isBadStockAndroid = (function() { 52 // Android stock browser test derived from 53 // http://stackoverflow.com/questions/24926221/distinguish-android-chrome-from-stock-browser-stock-browsers-user-agent-contai 54 var isAndroid = userAgent.indexOf(' Android ') > -1; 55 if (!isAndroid) { 56 return false; 57 } 58 59 var isStockAndroid = userAgent.indexOf('Version/') > -1; 60 if (!isStockAndroid) { 61 return false; 62 } 63 64 var versionNumber = parseFloat((userAgent.match('Android ([0-9.]+)') || [])[1]); 65 // anything below 4.4 uses WebKit without *any* viewport support, 66 // 4.4 has issues with viewport units within calc() 67 return versionNumber <= 4.4; 68 })(); 69 70 // added check for IE10, IE11 and Edge < 20, since it *still* doesn't understand vmax 71 // http://caniuse.com/#feat=viewport-units 72 if (!isBuggyIE) { 73 isBuggyIE = !!navigator.userAgent.match(/Trident.*rv[ :]*1[01]\.| Edge\/1\d\./); 74 } 75 76 // Polyfill for creating CustomEvents on IE9/10/11 77 // from https://github.com/krambuhl/custom-event-polyfill 78 try { 79 new CustomEvent('test'); 80 } catch(e) { 81 var CustomEvent = function(event, params) { 82 var evt; 83 params = params || { 84 bubbles: false, 85 cancelable: false, 86 detail: undefined 87 }; 88 89 evt = document.createEvent('CustomEvent'); 90 evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); 91 return evt; 92 }; 93 CustomEvent.prototype = window.Event.prototype; 94 window.CustomEvent = CustomEvent; // expose definition to window 95 } 96 97 function debounce(func, wait) { 98 var timeout; 99 return function() { 100 var context = this; 101 var args = arguments; 102 var callback = function() {
103 func.apply(context, args); 104 }; 105 106 clearTimeout(timeout); 107 timeout = setTimeout(callback, wait); 108 }; 109 } 110 111 // from http://stackoverflow.com/questions/326069/how-to-identify-if-a-webpage-is-being-loaded-inside-an-iframe-or-directly-into-t 112 function inIframe() { 113 try { 114 return window.self !== window.top; 115 } catch (e) { 116 return true; 117 } 118 } 119 120 function initialize(initOptions) { 121 if (initialized) { 122 return; 123 } 124 125 if (initOptions === true) { 126 initOptions = { 127 force: true 128 }; 129 } 130 131 options = initOptions || {}; 132 options.isMobileSafari = isMobileSafari; 133 options.isBadStockAndroid = isBadStockAndroid; 134 135 if (options.ignoreVmax && !options.force && !isOldIE) { 136 // modern IE (10 and up) do not support vmin/vmax, 137 // but chances are this unit is not even used, so 138 // allow overwriting the "hacktivation" 139 // https://github.com/rodneyrehm/viewport-units-buggyfill/issues/56 140 isBuggyIE = false; 141 } 142 143 if (isOldIE || (!options.force && !isMobileSafari && !isBuggyIE && !isBadStockAndroid && !isOperaMini && (!options.hacks || !options.hacks.required(options)))) { 144 // this buggyfill only applies to mobile safari, IE9-10 and the Stock Android Browser. 145 if (window.console && isOldIE) { 146 console.info('viewport-units-buggyfill requires a proper CSSOM and basic viewport unit support, which are not available in IE8 and below'); 147 } 148 149 return { 150 init: function () {} 151 }; 152 } 153 154 // fire a custom event that buggyfill was initialize 155 window.dispatchEvent(new CustomEvent('viewport-units-buggyfill-init')); 156 157 options.hacks && options.hacks.initialize(options); 158 159 initialized = true; 160 styleNode = document.createElement('style'); 161 styleNode.id = 'patched-viewport'; 162 document.head.appendChild(styleNode); 163 164 // Issue #6: Cross Origin Stylesheets are not accessible through CSSOM, 165 // therefore download and inject them as <style> to circumvent SOP. 166 importCrossOriginLinks(function() { 167 var _refresh = debounce(refresh, options.refreshDebounceWait || 100); 168 // doing a full refresh rather than updateStyles because an orientationchange 169 // could activate different stylesheets 170 window.addEventListener('orientationchange', _refresh, true); 171 // orientationchange might have happened while in a different window 172 window.addEventListener('pageshow', _refresh, true); 173 174 if (options.force || isBuggyIE || inIframe()) { 175 window.addEventListener('resize', _refresh, true); 176 options._listeningToResize = true; 177 } 178 179 options.hacks && options.hacks.initializeEvents(options, refresh, _refresh); 180 181 refresh(); 182 }); 183 } 184 185 function updateStyles() { 186 styleNode.textContent = getReplacedViewportUnits(); 187 // move to the end in case inline <style>s were added dynamically 188 styleNode.parentNode.appendChild(styleNode); 189 // fire a custom event that styles were updated 190 window.dispatchEvent(new CustomEvent('viewport-units-buggyfill-style')); 191 } 192 193 function refresh() { 194 if (!initialized) { 195 return; 196 } 197 198 findProperties(); 199 200 // iOS Safari will report window.innerWidth and .innerHeight as 0 unless a timeout is used here.
201 // TODO: figure out WHY innerWidth === 0 202 setTimeout(function() { 203 updateStyles(); 204 }, 1); 205 } 206 207 // http://stackoverflow.com/a/23613052 208 function processStylesheet(ss) { 209 // cssRules respects same-origin policy, as per 210 // https://code.google.com/p/chromium/issues/detail?id=49001#c10. 211 try { 212 if (!ss.cssRules) { return; } 213 } catch(e) { 214 if (e.name !== 'SecurityError') { throw e; } 215 return; 216 } 217 // ss.cssRules is available, so proceed with desired operations. 218 var rules = []; 219 for (var i = 0; i < ss.cssRules.length; i++) { 220 var rule = ss.cssRules[i]; 221 rules.push(rule); 222 } 223 return rules; 224 } 225 226 function findProperties() { 227 declarations = []; 228 forEach.call(document.styleSheets, function(sheet) { 229 var cssRules = processStylesheet(sheet); 230 231 if (!cssRules || sheet.ownerNode.id === 'patched-viewport' || sheet.ownerNode.getAttribute('data-viewport-units-buggyfill') === 'ignore') { 232 // skip entire sheet because no rules are present, it's supposed to be ignored or it's the target-element of the buggyfill 233 return; 234 } 235 236 if (sheet.media && sheet.media.mediaText && window.matchMedia && !window.matchMedia(sheet.media.mediaText).matches) { 237 // skip entire sheet because media attribute doesn't match 238 return; 239 } 240 241 forEach.call(cssRules, findDeclarations); 242 }); 243 244 return declarations; 245 } 246 247 function findDeclarations(rule) { 248 if (rule.type === 7) { 249 var value; 250 251 // there may be a case where accessing cssText throws an error. 252 // I could not reproduce this issue, but the worst that can happen 253 // this way is an animation not running properly. 254 // not awesome, but probably better than a script error 255 // see https://github.com/rodneyrehm/viewport-units-buggyfill/issues/21 256 try { 257 value = rule.cssText; 258 } catch(e) { 259 return; 260 } 261 262 viewportUnitExpression.lastIndex = 0; 263 if (viewportUnitExpression.test(value)) { 264 // KeyframesRule does not have a CSS-PropertyName 265 declarations.push([rule, null, value]); 266 options.hacks && options.hacks.findDeclarations(declarations, rule, null, value); 267 } 268 269 return; 270 } 271 272 if (!rule.style) { 273 if (!rule.cssRules) { 274 return; 275 } 276 277 forEach.call(rule.cssRules, function(_rule) { 278 findDeclarations(_rule); 279 }); 280 281 return; 282 } 283 284 forEach.call(rule.style, function(name) { 285 var value = rule.style.getPropertyValue(name); 286 // preserve those !important rules 287 if (rule.style.getPropertyPriority(name)) { 288 value += ' !important'; 289 } 290 291 viewportUnitExpression.lastIndex = 0; 292 if (viewportUnitExpression.test(value)) { 293 declarations.push([rule, name, value]); 294 options.hacks && options.hacks.findDeclarations(declarations, rule, name, value); 295 } 296 }); 297 } 298 299 function getReplacedViewportUnits() { 300 dimensions = getViewport(); 301 302 var css = []; 303 var buffer = []; 304 var open; 305 var close; 306 307 declarations.forEach(function(item) { 308 var _item = overwriteDeclaration.apply(null, item); 309 var _open = _item.selector.length ? (_item.selector.join(' {\n') + ' {\n') : ''; 310 var _close = new Array(_item.selector.length + 1).join('\n}'); 311 312 if (!_open || _open !== open) { 313 if (buffer.length) { 314 css.push(open + buffer.join('\n') + close); 315 buffer.length = 0; 316 } 317 318 if (_open) { 319 open = _open; 320 close = _close; 321 buffer.push(_item.content); 322 } else { 323 css.push(_item.content); 324 open = null; 325 close = null; 326 } 327 328 return; 329 } 330 331 if (_open && !open) { 332 open = _open; 333 close = _close; 334 } 335 336 buffer.push(_item.content); 337 }); 338 339 if (buffer.length) { 340 css.push(open + buffer.join('\n') + close); 341 } 342 343 // Opera Mini messes up on the content hack (it replaces the DOM node's innerHTML with the value). 344 // This fixes it. We test for Opera Mini only since it is the most expensive CSS selector 345 // see https://developer.mozilla.org/en-US/docs/Web/CSS/Universal_selectors 346 if (isOperaMini) { 347 css.push('* { content: normal !important; }'); 348 } 349 350 return css.join('\n\n'); 351 } 352 353 function overwriteDeclaration(rule, name, value) { 354 var _value; 355 var _selectors = []; 356 357 _value = value.replace(viewportUnitExpression, replaceValues); 358 359 if (options.hacks) { 360 _value = options.hacks.overwriteDeclaration(rule, name, _value); 361 } 362 363 if (name) { 364 // skipping KeyframesRule 365 _selectors.push(rule.selectorText); 366 _value = name + ': ' + _value + ';'; 367 } 368 369 var _rule = rule.parentRule; 370 while (_rule) { 371 _selectors.unshift('@media ' + _rule.media.mediaText); 372 _rule = _rule.parentRule; 373 } 374 375 return { 376 selector: _selectors, 377 content: _value 378 }; 379 } 380 381 function replaceValues(match, number, unit) { 382 var _base = dimensions[unit]; 383 var _number = parseFloat(number) / 100; 384 return (_number * _base) + 'px'; 385 } 386 387 function getViewport() { 388 var vh = window.innerHeight; 389 var vw = window.innerWidth; 390 391 return { 392 vh: vh, 393 vw: vw, 394 vmax: Math.max(vw, vh), 395 vmin: Math.min(vw, vh) 396 }; 397 } 398 399 function importCrossOriginLinks(next) { 400 var _waiting = 0; 401 var decrease = function() { 402 _waiting--; 403 if (!_waiting) { 404 next(); 405 } 406 }; 407
408 forEach.call(document.styleSheets, function(sheet) { 409 if (!sheet.href || origin(sheet.href) === origin(location.href) || sheet.ownerNode.getAttribute('data-viewport-units-buggyfill') === 'ignore') { 410 // skip <style> and <link> from same origin or explicitly declared to ignore 411 return; 412 } 413 414 _waiting++; 415 convertLinkToStyle(sheet.ownerNode, decrease); 416 }); 417 418 if (!_waiting) { 419 next(); 420 } 421 } 422 423 function origin(url) { 424 return url.slice(0, url.indexOf('/', url.indexOf('://') + 3)); 425 } 426 427 function convertLinkToStyle(link, next) { 428 getCors(link.href, function() { 429 var style = document.createElement('style'); 430 style.media = link.media; 431 style.setAttribute('data-href', link.href); 432 style.textContent = this.responseText; 433 link.parentNode.replaceChild(style, link); 434 next(); 435 }, next); 436 } 437 438 function getCors(url, success, error) { 439 var xhr = new XMLHttpRequest(); 440 if ('withCredentials' in xhr) { 441 // XHR for Chrome/Firefox/Opera/Safari. 442 xhr.open('GET', url, true); 443 } else if (typeof XDomainRequest !== 'undefined') { 444 // XDomainRequest for IE. 445 xhr = new XDomainRequest(); 446 xhr.open('GET', url); 447 } else { 448 throw new Error('cross-domain XHR not supported'); 449 } 450 451 xhr.onload = success; 452 xhr.onerror = error; 453 xhr.send(); 454 return xhr; 455 } 456 457 return { 458 version: '0.6.0', 459 findProperties: findProperties, 460 getCss: getReplacedViewportUnits, 461 init: initialize, 462 refresh: refresh 463 }; 464 465}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.