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