browsers/polyfill.js

Summary

Maintainability
D
2 days
Test Coverage
/* eslint-disable */
/*!
Copyright (C) 2013-2015 by WebReflection

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/
(function(window){
    /*! (C) WebReflection Mit Style License */
    if (document.createEvent) return;
    var
      DUNNOABOUTDOMLOADED = true,
      READYEVENTDISPATCHED = false,
      ONREADYSTATECHANGE = 'onreadystatechange',
      DOMCONTENTLOADED = 'DOMContentLoaded',
      SECRET = '__IE8__' + Math.random(),
      // Object = window.Object,
      defineProperty = Object.defineProperty ||
      // just in case ...
      function (object, property, descriptor) {
        object[property] = descriptor.value;
      },
      defineProperties = Object.defineProperties ||
      // IE8 implemented defineProperty but not the plural...
      function (object, descriptors) {
        for(var key in descriptors) {
          if (hasOwnProperty.call(descriptors, key)) {
            try {
              defineProperty(object, key, descriptors[key]);
            } catch(o_O) {
              if (window.console) {
                console.log(key + ' failed on object:', object, o_O.message);
              }
            }
          }
        }
      },
      getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor,
      hasOwnProperty = Object.prototype.hasOwnProperty,
      // here IE7 will break like a charm
      ElementPrototype = window.Element.prototype,
      TextPrototype = window.Text.prototype,
      // none of above native constructors exist/are exposed
      possiblyNativeEvent = /^[a-z]+$/,
      // ^ actually could probably be just /^[a-z]+$/
      readyStateOK = /loaded|complete/,
      types = {},
      div = document.createElement('div'),
      html = document.documentElement,
      removeAttribute = html.removeAttribute,
      setAttribute = html.setAttribute,
      valueDesc = function (value) {
        return {
          enumerable: true,
          writable: true,
          configurable: true,
          value: value
        };
      }
    ;
  
    function commonEventLoop(currentTarget, e, $handlers, synthetic) {
      for(var
        handler,
        continuePropagation,
        handlers = $handlers.slice(),
        evt = enrich(e, currentTarget),
        i = 0, length = handlers.length; i < length; i++
      ) {
        handler = handlers[i];
        if (typeof handler === 'object') {
          if (typeof handler.handleEvent === 'function') {
            handler.handleEvent(evt);
          }
        } else {
          handler.call(currentTarget, evt);
        }
        if (evt.stoppedImmediatePropagation) break;
      }
      continuePropagation = !evt.stoppedPropagation;
      /*
      if (continuePropagation && !synthetic && !live(currentTarget)) {
        evt.cancelBubble = true;
      }
      */
      return (
        synthetic &&
        continuePropagation &&
        currentTarget.parentNode
      ) ?
        currentTarget.parentNode.dispatchEvent(evt) :
        !evt.defaultPrevented
      ;
    }
  
    function commonDescriptor(get, set) {
      return {
        // if you try with enumerable: true
        // IE8 will miserably fail
        configurable: true,
        get: get,
        set: set
      };
    }
  
    function commonTextContent(protoDest, protoSource, property) {
      var descriptor = getOwnPropertyDescriptor(
        protoSource || protoDest, property
      );
      defineProperty(
        protoDest,
        'textContent',
        commonDescriptor(
          function () {
            return descriptor.get.call(this);
          },
          function (textContent) {
            descriptor.set.call(this, textContent);
          }
        )
      );
    }
  
    function enrich(e, currentTarget) {
      e.currentTarget = currentTarget;
      e.eventPhase = (
        // AT_TARGET : BUBBLING_PHASE
        e.target === e.currentTarget ? 2 : 3
      );
      return e;
    }
  
    function find(array, value) {
      var i = array.length;
      while(i-- && array[i] !== value);
      return i;
    }
  
    function getTextContent() {
      if (this.tagName === 'BR') return '\n';
      var
        textNode = this.firstChild,
        arrayContent = []
      ;
      while(textNode) {
        if (textNode.nodeType !== 8 && textNode.nodeType !== 7) {
          arrayContent.push(textNode.textContent);
        }
        textNode = textNode.nextSibling;
      }
      return arrayContent.join('');
    }
  
    function live(self) {
      return self.nodeType !== 9 && html.contains(self);
    }
  
    function onkeyup(e) {
      var evt = document.createEvent('Event');
      evt.initEvent('input', true, true);
      (e.srcElement || e.fromElement || document).dispatchEvent(evt);
    }
  
    function onReadyState(e) {
      if (!READYEVENTDISPATCHED && readyStateOK.test(
        document.readyState
      )) {
        READYEVENTDISPATCHED = !READYEVENTDISPATCHED;
        document.detachEvent(ONREADYSTATECHANGE, onReadyState);
        e = document.createEvent('Event');
        e.initEvent(DOMCONTENTLOADED, true, true);
        document.dispatchEvent(e);
      }
    }
    
    function getter(attr) {
      return function() {
        return html[attr] || (document.body && document.body[attr]) || 0;
      };
    }
  
    function setTextContent(textContent) {
      var node;
      while ((node = this.lastChild)) {
        this.removeChild(node);
      }
      /*jshint eqnull:true */
      if (textContent != null) {
        this.appendChild(document.createTextNode(textContent));
      }
    }
  
    function verify(self, e) {
      if (!e) {
        e = window.event;
      }
      if (!e.target) {
        e.target = e.srcElement || e.fromElement || document;
      }
      if (!e.timeStamp) {
        e.timeStamp = (new Date()).getTime();
      }
      return e;
    }
  
    // normalized textContent for:
    //  comment, script, style, text, title
    commonTextContent(
      window.HTMLCommentElement.prototype,
      ElementPrototype,
      'nodeValue'
    );
  
    commonTextContent(
      window.HTMLScriptElement.prototype,
      null,
      'text'
    );
  
    commonTextContent(
      TextPrototype,
      null,
      'nodeValue'
    );
  
    commonTextContent(
      window.HTMLTitleElement.prototype,
      null,
      'text'
    );
  
    defineProperty(
      window.HTMLStyleElement.prototype,
      'textContent',
      (function(descriptor){
        return commonDescriptor(
          function () {
            return descriptor.get.call(this.styleSheet);
          },
          function (textContent) {
            descriptor.set.call(this.styleSheet, textContent);
          }
        );
      }(getOwnPropertyDescriptor(window.CSSStyleSheet.prototype, 'cssText')))
    );
  
    var opacityre = /\b\s*alpha\s*\(\s*opacity\s*=\s*(\d+)\s*\)/;
    defineProperty(
      window.CSSStyleDeclaration.prototype,
      'opacity', {
        get: function() {
          var m = this.filter.match(opacityre);
          return m ? (m[1] / 100).toString() : '';
        },
        set: function(value) {
          this.zoom = 1;
          var found = false;
          if (value < 1) {
            value = ' alpha(opacity=' + Math.round(value * 100) + ')';
          }
          else {
            value = '';
          }
          this.filter = this.filter.replace(opacityre,
                          function() { found = true; return value; });
          if (!found && value) {
            this.filter += value;
          }
        }
      }
    );
  
    defineProperties(
      ElementPrototype,
      {
        // bonus
        textContent: {
          get: getTextContent,
          set: setTextContent
        },
        // http://www.w3.org/TR/ElementTraversal/#interface-elementTraversal
        firstElementChild: {
          get: function () {
            for(var
              childNodes = this.childNodes || [],
              i = 0, length = childNodes.length;
              i < length; i++
            ) {
              if (childNodes[i].nodeType == 1) return childNodes[i];
            }
          }
        },
        lastElementChild: {
          get: function () {
            for(var
              childNodes = this.childNodes || [],
              i = childNodes.length;
              i--;
            ) {
              if (childNodes[i].nodeType == 1) return childNodes[i];
            }
          }
        },
        oninput: {
          get: function () {
            return this._oninput || null;
          },
          set: function (oninput) {
            if (this._oninput) {
              this.removeEventListener('input', this._oninput);
              this._oninput = oninput;
              if (oninput) {
                this.addEventListener('input', oninput);
              }
            }
          }
        },
        previousElementSibling: {
          get: function () {
            var previousElementSibling = this.previousSibling;
            while (previousElementSibling && previousElementSibling.nodeType != 1) {
              previousElementSibling = previousElementSibling.previousSibling;
            }
            return previousElementSibling;
          }
        },
        nextElementSibling: {
          get: function () {
            var nextElementSibling = this.nextSibling;
            while (nextElementSibling && nextElementSibling.nodeType != 1) {
              nextElementSibling = nextElementSibling.nextSibling;
            }
            return nextElementSibling;
          }
        },
        childElementCount: {
          get: function () {
            for(var
              count = 0,
              childNodes = this.childNodes || [],
              i = childNodes.length; i--; count += childNodes[i].nodeType == 1
            );
            return count;
          }
        },
        /*
        // children would be an override
        // IE8 already supports them but with comments too
        // not just nodeType 1
        children: {
          get: function () {
            for(var
              children = [],
              childNodes = this.childNodes || [],
              i = 0, length = childNodes.length;
              i < length; i++
            ) {
              if (childNodes[i].nodeType == 1) {
                children.push(childNodes[i]);
              }
            }
            return children;
          }
        },
        */
        // DOM Level 2 EventTarget methods and events
        addEventListener: valueDesc(function (type, handler, capture) {
          if (typeof handler !== 'function' && typeof handler !== 'object') return;
          var
            self = this,
            ontype = 'on' + type,
            temple =  self[SECRET] ||
                        defineProperty(
                          self, SECRET, {value: {}}
                        )[SECRET],
            currentType = temple[ontype] || (temple[ontype] = {}),
            handlers  = currentType.h || (currentType.h = []),
            e, attr
          ;
          if (!hasOwnProperty.call(currentType, 'w')) {
            currentType.w = function (e) {
              // e[SECRET] is a silent notification needed to avoid
              // fired events during live test
              return e[SECRET] || commonEventLoop(self, verify(self, e), handlers, false);
            };
            // if not detected yet
            if (!hasOwnProperty.call(types, ontype)) {
              // and potentially a native event
              if(possiblyNativeEvent.test(type)) {
                // do this heavy thing
                try {
                  // TODO:  should I consider tagName too so that
                  //        INPUT[ontype] could be different ?
                  e = document.createEventObject();
                  // do not clone ever a node
                  // specially a document one ...
                  // use the secret to ignore them all
                  e[SECRET] = true;
                  // document a part if a node has never been
                  // added to any other node, fireEvent might
                  // behave very weirdly (read: trigger unspecified errors)
                  if (self.nodeType != 9) {
                    /*jshint eqnull:true */
                    if (self.parentNode == null) {
                      div.appendChild(self);
                    }
                    if ((attr = self.getAttribute(ontype))) {
                      removeAttribute.call(self, ontype);
                    }
                  }
                  self.fireEvent(ontype, e);
                  types[ontype] = true;
                } catch(meh) {
                  types[ontype] = false;
                  while (div.hasChildNodes()) {
                    div.removeChild(div.firstChild);
                  }
                }
                if (attr != null) {
                  setAttribute.call(self, ontype, attr);
                }
              } else {
                // no need to bother since
                // 'x-event' ain't native for sure
                types[ontype] = false;
              }
            }
            if ((currentType.n = types[ontype])) {
              self.attachEvent(ontype, currentType.w);
            }
          }
          if (find(handlers, handler) < 0) {
            handlers[capture ? 'unshift' : 'push'](handler);
          }
          if (type === 'input') {
            self.attachEvent('onkeyup', onkeyup);
          }
        }),
        dispatchEvent: valueDesc(function (e) {
          var
            self = this,
            ontype = 'on' + e.type,
            temple =  self[SECRET],
            currentType = temple && temple[ontype],
            valid = !!currentType,
            parentNode
          ;
          if (!e.target) e.target = self;
          return (valid ? (
            currentType.n /* && live(self) */ ?
              self.fireEvent(ontype, e) :
              commonEventLoop(
                self,
                e,
                currentType.h,
                true
              )
          ) : (
            (parentNode = self.parentNode) /* && live(self) */ ?
              parentNode.dispatchEvent(e) :
              true
          )), !e.defaultPrevented;
        }),
        removeEventListener: valueDesc(function (type, handler, capture) {
          if (typeof handler !== 'function' && typeof handler !== 'object') return;
          var
            self = this,
            ontype = 'on' + type,
            temple =  self[SECRET],
            currentType = temple && temple[ontype],
            handlers = currentType && currentType.h,
            i = handlers ? find(handlers, handler) : -1
          ;
          if (-1 < i) handlers.splice(i, 1);
        })
      }
    );
  
    /* this is not needed in IE8
    defineProperties(window.HTMLSelectElement.prototype, {
      value: {
        get: function () {
          return this.options[this.selectedIndex].value;
        }
      }
    });
    //*/
  
    // EventTarget methods for Text nodes too
    defineProperties(TextPrototype, {
      addEventListener: valueDesc(ElementPrototype.addEventListener),
      dispatchEvent: valueDesc(ElementPrototype.dispatchEvent),
      removeEventListener: valueDesc(ElementPrototype.removeEventListener)
    });
  
    defineProperties(
      window.XMLHttpRequest.prototype,
      {
        addEventListener: valueDesc(function (type, handler, capture) {
          var
            self = this,
            ontype = 'on' + type,
            temple =  self[SECRET] ||
                        defineProperty(
                          self, SECRET, {value: {}}
                        )[SECRET],
            currentType = temple[ontype] || (temple[ontype] = {}),
            handlers  = currentType.h || (currentType.h = [])
          ;
          if (find(handlers, handler) < 0) {
            if (!self[ontype]) {
              self[ontype] = function () {
                var e = document.createEvent('Event');
                e.initEvent(type, true, true);
                self.dispatchEvent(e);
              };
            }
            handlers[capture ? 'unshift' : 'push'](handler);
          }
        }),
        dispatchEvent: valueDesc(function (e) {
          var
            self = this,
            ontype = 'on' + e.type,
            temple =  self[SECRET],
            currentType = temple && temple[ontype],
            valid = !!currentType
          ;
          return valid && (
            currentType.n /* && live(self) */ ?
              self.fireEvent(ontype, e) :
              commonEventLoop(
                self,
                e,
                currentType.h,
                true
              )
          );
        }),
        removeEventListener: valueDesc(ElementPrototype.removeEventListener)
      }
    );
  
    var buttonGetter = getOwnPropertyDescriptor(Event.prototype, 'button').get;
    defineProperties(
      window.Event.prototype,
      {
        bubbles: valueDesc(true),
        cancelable: valueDesc(true),
        preventDefault: valueDesc(function () {
          if (this.cancelable) {
            this.returnValue = false;
          }
        }),
        stopPropagation: valueDesc(function () {
          this.stoppedPropagation = true;
          this.cancelBubble = true;
        }),
        stopImmediatePropagation: valueDesc(function () {
          this.stoppedImmediatePropagation = true;
          this.stopPropagation();
        }),
        initEvent: valueDesc(function(type, bubbles, cancelable){
          this.type = type;
          this.bubbles = !!bubbles;
          this.cancelable = !!cancelable;
          if (!this.bubbles) {
            this.stopPropagation();
          }
        }),
        pageX: {get: function(){ return this._pageX || (this._pageX = this.clientX + window.scrollX - (html.clientLeft || 0)); }},
        pageY: {get: function(){ return this._pageY || (this._pageY = this.clientY + window.scrollY - (html.clientTop || 0)); }},
        which: {get: function(){ return this.keyCode ? this.keyCode : (isNaN(this.button) ? undefined : this.button + 1); }},
        charCode: {get: function(){ return (this.keyCode && this.type == 'keypress') ? this.keyCode : 0; }},
        buttons: {get: function(){ return buttonGetter.call(this); }},
        button: {get: function() {
          var buttons = this.buttons;
          return (buttons & 1 ? 0 : (buttons & 2 ? 2 : (buttons & 4 ? 1 : undefined)));
        }},
        defaultPrevented: {get: function() {
          // if preventDefault() was never called, or returnValue not given a value
          // then returnValue is undefined
          var returnValue = this.returnValue, undef;
          return !(returnValue === undef || returnValue);
        }},
        relatedTarget: {get: function() {
          var type = this.type;
          if (type === 'mouseover') {
            return this.fromElement;
          } else if (type === 'mouseout') {
            return this.toElement;
          } else {
            return null;
          }
        }}
      }
    );
  
    defineProperties(
      window.HTMLDocument.prototype,
      {
        defaultView: {
          get: function () {
            return this.parentWindow;
          }
        },
        textContent: {
          get: function () {
            return this.nodeType === 11 ? getTextContent.call(this) : null;
          },
          set: function (textContent) {
            if (this.nodeType === 11) {
              setTextContent.call(this, textContent);
            }
          }
        },
        addEventListener: valueDesc(function(type, handler, capture) {
          var self = this;
          ElementPrototype.addEventListener.call(self, type, handler, capture);
          // NOTE:  it won't fire if already loaded, this is NOT a $.ready() shim!
          //        this behaves just like standard browsers
          if (
            DUNNOABOUTDOMLOADED &&
            type === DOMCONTENTLOADED &&
            !readyStateOK.test(
              self.readyState
            )
          ) {
            DUNNOABOUTDOMLOADED = false;
            self.attachEvent(ONREADYSTATECHANGE, onReadyState);
            /* global top */
            if (window == top) {
              (function gonna(e){try{
                self.documentElement.doScroll('left');
                onReadyState();
                }catch(o_O){
                setTimeout(gonna, 50);
              }}());
            }
          }
        }),
        dispatchEvent: valueDesc(ElementPrototype.dispatchEvent),
        removeEventListener: valueDesc(ElementPrototype.removeEventListener),
        createEvent: valueDesc(function(Class){
          var e;
          if (Class !== 'Event') throw new Error('unsupported ' + Class);
          e = document.createEventObject();
          e.timeStamp = (new Date()).getTime();
          return e;
        })
      }
    );
  
    defineProperties(
      window.Window.prototype,
      {
        getComputedStyle: valueDesc(function(){
  
          var // partially grabbed from jQuery and Dean's hack
            notpixel = /^(?:[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|))(?!px)[a-z%]+$/,
            position = /^(top|right|bottom|left)$/,
            re = /\-([a-z])/g,
            place = function (match, $1) {
              return $1.toUpperCase();
            }
          ;
  
          function ComputedStyle(_) {
            this._ = _;
          }
  
          ComputedStyle.prototype.getPropertyValue = function (name) {
            var
              el = this._,
              style = el.style,
              currentStyle = el.currentStyle,
              runtimeStyle = el.runtimeStyle,
              result,
              left,
              rtLeft
            ;
            if (name == 'opacity') {
              return style.opacity || '1';
            }
            name = (name === 'float' ? 'style-float' : name).replace(re, place);
            result = currentStyle ? currentStyle[name] : style[name];
            if (notpixel.test(result) && !position.test(name)) {
              left = style.left;
              rtLeft = runtimeStyle && runtimeStyle.left;
              if (rtLeft) {
                runtimeStyle.left = currentStyle.left;
              }
              style.left = name === 'fontSize' ? '1em' : result;
              result = style.pixelLeft + 'px';
              style.left = left;
              if (rtLeft) {
                runtimeStyle.left = rtLeft;
              }
            }
            /*jshint eqnull:true */
            return result == null ?
              result : ((result + '') || 'auto');
          };
  
          // unsupported
          function PseudoComputedStyle() {}
          PseudoComputedStyle.prototype.getPropertyValue = function () {
            return null;
          };
  
          return function (el, pseudo) {
            return pseudo ?
              new PseudoComputedStyle(el) :
              new ComputedStyle(el);
          };
  
        }()),
  
        addEventListener: valueDesc(function (type, handler, capture) {
          var
            self = window,
            ontype = 'on' + type,
            handlers
          ;
          if (!self[ontype]) {
            self[ontype] = function(e) {
              return commonEventLoop(self, verify(self, e), handlers, false) && undefined;
            };
          }
          handlers = self[ontype][SECRET] || (
            self[ontype][SECRET] = []
          );
          if (find(handlers, handler) < 0) {
            handlers[capture ? 'unshift' : 'push'](handler);
          }
        }),
        dispatchEvent: valueDesc(function (e) {
          var method = window['on' + e.type];
          return method ? method.call(window, e) !== false && !e.defaultPrevented : true;
        }),
        removeEventListener: valueDesc(function (type, handler, capture) {
          var
            ontype = 'on' + type,
            handlers = (window[ontype] || Object)[SECRET],
            i = handlers ? find(handlers, handler) : -1
           ;
          if (-1 < i) handlers.splice(i, 1);
        }),
        pageXOffset: {get: getter('scrollLeft')},
        pageYOffset: {get: getter('scrollTop')},
        scrollX: {get: getter('scrollLeft')},
        scrollY: {get: getter('scrollTop')},
        innerWidth: {get: getter('clientWidth')},
        innerHeight: {get: getter('clientHeight')}
      }
    );
  
    window.HTMLElement = window.Element;
  
    (function (styleSheets, HTML5Element, i) {
      for (i = 0; i < HTML5Element.length; i++) document.createElement(HTML5Element[i]);
      if (!styleSheets.length) document.createStyleSheet('');
      styleSheets[0].addRule(HTML5Element.join(','), 'display:block;');
    }(document.styleSheets, ['header', 'nav', 'section', 'article', 'aside', 'footer']));
  }(window || global));