UNPKG

dom4

Version:

a fully tested and covered polyfill for new DOM Level 4 entries

986 lines (935 loc) 30 kB
/*! Copyright (C) 2013-2015 by Andrea Giammarchi - @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){'use strict'; /* jshint loopfunc: true, noempty: false*/ // http://www.w3.org/TR/dom/#element function createDocumentFragment() { return document.createDocumentFragment(); } function createElement(nodeName) { return document.createElement(nodeName); } function enoughArguments(length, name) { if (!length) throw new Error( 'Failed to construct ' + name + ': 1 argument required, but only 0 present.' ); } function mutationMacro(nodes) { if (nodes.length === 1) { return textNodeIfPrimitive(nodes[0]); } for (var fragment = createDocumentFragment(), list = slice.call(nodes), i = 0; i < nodes.length; i++ ) { fragment.appendChild(textNodeIfPrimitive(list[i])); } return fragment; } function textNodeIfPrimitive(node) { return typeof node === 'object' ? node : document.createTextNode(node); } for(var head, property, TemporaryPrototype, TemporaryTokenList, wrapVerifyToken, document = window.document, hOP = Object.prototype.hasOwnProperty, defineProperty = Object.defineProperty || function (object, property, descriptor) { if (hOP.call(descriptor, 'value')) { object[property] = descriptor.value; } else { if (hOP.call(descriptor, 'get')) object.__defineGetter__(property, descriptor.get); if (hOP.call(descriptor, 'set')) object.__defineSetter__(property, descriptor.set); } return object; }, indexOf = [].indexOf || function indexOf(value){ var length = this.length; while(length--) { if (this[length] === value) { break; } } return length; }, // http://www.w3.org/TR/domcore/#domtokenlist verifyToken = function (token) { if (!token) { throw 'SyntaxError'; } else if (spaces.test(token)) { throw 'InvalidCharacterError'; } return token; }, DOMTokenList = function (node) { var noClassName = typeof node.className === 'undefined', className = noClassName ? (node.getAttribute('class') || '') : node.className, isSVG = noClassName || typeof className === 'object', value = (isSVG ? (noClassName ? className : className.baseVal) : className ).replace(trim, '') ; if (value.length) { properties.push.apply( this, value.split(spaces) ); } this._isSVG = isSVG; this._ = node; }, classListDescriptor = { get: function get() { return new DOMTokenList(this); }, set: function(){} }, trim = /^\s+|\s+$/g, spaces = /\s+/, SPACE = '\x20', CLASS_LIST = 'classList', toggle = function toggle(token, force) { if (this.contains(token)) { if (!force) { // force is not true (either false or omitted) this.remove(token); } } else if(force === undefined || force) { force = true; this.add(token); } return !!force; }, DocumentFragmentPrototype = window.DocumentFragment && DocumentFragment.prototype, Node = window.Node, NodePrototype = (Node || Element).prototype, CharacterData = window.CharacterData || Node, CharacterDataPrototype = CharacterData && CharacterData.prototype, DocumentType = window.DocumentType, DocumentTypePrototype = DocumentType && DocumentType.prototype, ElementPrototype = (window.Element || Node || window.HTMLElement).prototype, HTMLSelectElement = window.HTMLSelectElement || createElement('select').constructor, selectRemove = HTMLSelectElement.prototype.remove, SVGElement = window.SVGElement, properties = [ 'matches', ( ElementPrototype.matchesSelector || ElementPrototype.webkitMatchesSelector || ElementPrototype.khtmlMatchesSelector || ElementPrototype.mozMatchesSelector || ElementPrototype.msMatchesSelector || ElementPrototype.oMatchesSelector || function matches(selector) { var parentNode = this.parentNode; return !!parentNode && -1 < indexOf.call( parentNode.querySelectorAll(selector), this ); } ), 'closest', function closest(selector) { var parentNode = this, matches; while ( // document has no .matches (matches = parentNode && parentNode.matches) && !parentNode.matches(selector) ) { parentNode = parentNode.parentNode; } return matches ? parentNode : null; }, 'prepend', function prepend() { var firstChild = this.firstChild, node = mutationMacro(arguments); if (firstChild) { this.insertBefore(node, firstChild); } else { this.appendChild(node); } }, 'append', function append() { this.appendChild(mutationMacro(arguments)); }, 'before', function before() { var parentNode = this.parentNode; if (parentNode) { parentNode.insertBefore( mutationMacro(arguments), this ); } }, 'after', function after() { var parentNode = this.parentNode, nextSibling = this.nextSibling, node = mutationMacro(arguments); if (parentNode) { if (nextSibling) { parentNode.insertBefore(node, nextSibling); } else { parentNode.appendChild(node); } } }, // https://dom.spec.whatwg.org/#dom-element-toggleattribute 'toggleAttribute', function toggleAttribute(name, force) { var had = this.hasAttribute(name); if (1 < arguments.length) { if (had && !force) this.removeAttribute(name); else if (force && !had) this.setAttribute(name, ""); } else if (had) this.removeAttribute(name); else this.setAttribute(name, ""); return this.hasAttribute(name); }, // WARNING - DEPRECATED - use .replaceWith() instead 'replace', function replace() { this.replaceWith.apply(this, arguments); }, 'replaceWith', function replaceWith() { var parentNode = this.parentNode; if (parentNode) { parentNode.replaceChild( mutationMacro(arguments), this ); } }, 'remove', function remove() { var parentNode = this.parentNode; if (parentNode) { parentNode.removeChild(this); } } ], slice = properties.slice, i = properties.length; i; i -= 2 ) { property = properties[i - 2]; if (!(property in ElementPrototype)) { ElementPrototype[property] = properties[i - 1]; } // avoid unnecessary re-patch when the script is included // gazillion times without any reason whatsoever // https://github.com/WebReflection/dom4/pull/48 if (property === 'remove' && !selectRemove._dom4) { // see https://github.com/WebReflection/dom4/issues/19 (HTMLSelectElement.prototype[property] = function () { return 0 < arguments.length ? selectRemove.apply(this, arguments) : ElementPrototype.remove.call(this); })._dom4 = true; } // see https://github.com/WebReflection/dom4/issues/18 if (/^(?:before|after|replace|replaceWith|remove)$/.test(property)) { if (CharacterData && !(property in CharacterDataPrototype)) { CharacterDataPrototype[property] = properties[i - 1]; } if (DocumentType && !(property in DocumentTypePrototype)) { DocumentTypePrototype[property] = properties[i - 1]; } } // see https://github.com/WebReflection/dom4/pull/26 if (/^(?:append|prepend)$/.test(property)) { if (DocumentFragmentPrototype) { if (!(property in DocumentFragmentPrototype)) { DocumentFragmentPrototype[property] = properties[i - 1]; } } else { try { createDocumentFragment().constructor.prototype[property] = properties[i - 1]; } catch(o_O) {} } } } // most likely an IE9 only issue // see https://github.com/WebReflection/dom4/issues/6 if (!createElement('a').matches('a')) { ElementPrototype[property] = function(matches){ return function (selector) { return matches.call( this.parentNode ? this : createDocumentFragment().appendChild(this), selector ); }; }(ElementPrototype[property]); } // used to fix both old webkit and SVG DOMTokenList.prototype = { length: 0, add: function add() { for(var j = 0, token; j < arguments.length; j++) { token = arguments[j]; if(!this.contains(token)) { properties.push.call(this, property); } } if (this._isSVG) { this._.setAttribute('class', '' + this); } else { this._.className = '' + this; } }, contains: (function(indexOf){ return function contains(token) { i = indexOf.call(this, property = verifyToken(token)); return -1 < i; }; }([].indexOf || function (token) { i = this.length; while(i-- && this[i] !== token){} return i; })), item: function item(i) { return this[i] || null; }, remove: function remove() { for(var j = 0, token; j < arguments.length; j++) { token = arguments[j]; if(this.contains(token)) { properties.splice.call(this, i, 1); } } if (this._isSVG) { this._.setAttribute('class', '' + this); } else { this._.className = '' + this; } }, toggle: toggle, toString: function toString() { return properties.join.call(this, SPACE); } }; if (SVGElement && !(CLASS_LIST in SVGElement.prototype)) { defineProperty(SVGElement.prototype, CLASS_LIST, classListDescriptor); } // http://www.w3.org/TR/dom/#domtokenlist // iOS 5.1 has completely screwed this property // classList in ElementPrototype is false // but it's actually there as getter if (!(CLASS_LIST in document.documentElement)) { defineProperty(ElementPrototype, CLASS_LIST, classListDescriptor); } else { // iOS 5.1 and Nokia ASHA do not support multiple add or remove // trying to detect and fix that in here TemporaryTokenList = createElement('div')[CLASS_LIST]; TemporaryTokenList.add('a', 'b', 'a'); if ('a\x20b' != TemporaryTokenList) { // no other way to reach original methods in iOS 5.1 TemporaryPrototype = TemporaryTokenList.constructor.prototype; if (!('add' in TemporaryPrototype)) { // ASHA double fails in here TemporaryPrototype = window.TemporaryTokenList.prototype; } wrapVerifyToken = function (original) { return function () { var i = 0; while (i < arguments.length) { original.call(this, arguments[i++]); } }; }; TemporaryPrototype.add = wrapVerifyToken(TemporaryPrototype.add); TemporaryPrototype.remove = wrapVerifyToken(TemporaryPrototype.remove); // toggle is broken too ^_^ ... let's fix it TemporaryPrototype.toggle = toggle; } } if (!('contains' in NodePrototype)) { defineProperty(NodePrototype, 'contains', { value: function (el) { while (el && el !== this) el = el.parentNode; return this === el; } }); } if (!('head' in document)) { defineProperty(document, 'head', { get: function () { return head || ( head = document.getElementsByTagName('head')[0] ); } }); } // requestAnimationFrame partial polyfill (function () { for (var raf, rAF = window.requestAnimationFrame, cAF = window.cancelAnimationFrame, prefixes = ['o', 'ms', 'moz', 'webkit'], i = prefixes.length; !cAF && i--; ) { rAF = rAF || window[prefixes[i] + 'RequestAnimationFrame']; cAF = window[prefixes[i] + 'CancelAnimationFrame'] || window[prefixes[i] + 'CancelRequestAnimationFrame']; } if (!cAF) { // some FF apparently implemented rAF but no cAF if (rAF) { raf = rAF; rAF = function (callback) { var goOn = true; raf(function () { if (goOn) callback.apply(this, arguments); }); return function () { goOn = false; }; }; cAF = function (id) { id(); }; } else { rAF = function (callback) { return setTimeout(callback, 15, 15); }; cAF = function (id) { clearTimeout(id); }; } } window.requestAnimationFrame = rAF; window.cancelAnimationFrame = cAF; }()); // http://www.w3.org/TR/dom/#customevent try{new window.CustomEvent('?');}catch(o_O){ window.CustomEvent = function( eventName, defaultInitDict ){ // the infamous substitute function CustomEvent(type, eventInitDict) { /*jshint eqnull:true */ var event = document.createEvent(eventName); if (typeof type != 'string') { throw new Error('An event name must be provided'); } if (eventName == 'Event') { event.initCustomEvent = initCustomEvent; } if (eventInitDict == null) { eventInitDict = defaultInitDict; } event.initCustomEvent( type, eventInitDict.bubbles, eventInitDict.cancelable, eventInitDict.detail ); return event; } // attached at runtime function initCustomEvent( type, bubbles, cancelable, detail ) { /*jshint validthis:true*/ this.initEvent(type, bubbles, cancelable); this.detail = detail; } // that's it return CustomEvent; }( // is this IE9 or IE10 ? // where CustomEvent is there // but not usable as construtor ? window.CustomEvent ? // use the CustomEvent interface in such case 'CustomEvent' : 'Event', // otherwise the common compatible one { bubbles: false, cancelable: false, detail: null } ); } // window.Event as constructor try { new Event('_'); } catch (o_O) { /* jshint -W022 */ o_O = (function ($Event) { function Event(type, init) { enoughArguments(arguments.length, 'Event'); var out = document.createEvent('Event'); if (!init) init = {}; out.initEvent( type, !!init.bubbles, !!init.cancelable ); return out; } Event.prototype = $Event.prototype; return Event; }(window.Event || function Event() {})); defineProperty(window, 'Event', {value: o_O}); // Android 4 gotcha if (Event !== o_O) Event = o_O; } // window.KeyboardEvent as constructor try { new KeyboardEvent('_', {}); } catch (o_O) { /* jshint -W022 */ o_O = (function ($KeyboardEvent) { // code inspired by https://gist.github.com/termi/4654819 var initType = 0, defaults = { char: '', key: '', location: 0, ctrlKey: false, shiftKey: false, altKey: false, metaKey: false, altGraphKey: false, repeat: false, locale: navigator.language, detail: 0, bubbles: false, cancelable: false, keyCode: 0, charCode: 0, which: 0 }, eventType ; try { var e = document.createEvent('KeyboardEvent'); e.initKeyboardEvent( 'keyup', false, false, window, '+', 3, true, false, true, false, false ); initType = ( (e.keyIdentifier || e.key) == '+' && (e.keyLocation || e.location) == 3 ) && ( e.ctrlKey ? e.altKey ? 1 : 3 : e.shiftKey ? 2 : 4 ) || 9; } catch(o_O) {} eventType = 0 < initType ? 'KeyboardEvent' : 'Event'; function getModifier(init) { for (var out = [], keys = [ 'ctrlKey', 'Control', 'shiftKey', 'Shift', 'altKey', 'Alt', 'metaKey', 'Meta', 'altGraphKey', 'AltGraph' ], i = 0; i < keys.length; i += 2 ) { if (init[keys[i]]) out.push(keys[i + 1]); } return out.join(' '); } function withDefaults(target, source) { for (var key in source) { if ( source.hasOwnProperty(key) && !source.hasOwnProperty.call(target, key) ) target[key] = source[key]; } return target; } function withInitValues(key, out, init) { try { out[key] = init[key]; } catch(o_O) {} } function KeyboardEvent(type, init) { enoughArguments(arguments.length, 'KeyboardEvent'); init = withDefaults(init || {}, defaults); var out = document.createEvent(eventType), ctrlKey = init.ctrlKey, shiftKey = init.shiftKey, altKey = init.altKey, metaKey = init.metaKey, altGraphKey = init.altGraphKey, modifiers = initType > 3 ? getModifier(init) : null, key = String(init.key), chr = String(init.char), location = init.location, keyCode = init.keyCode || ( (init.keyCode = key) && key.charCodeAt(0) ) || 0, charCode = init.charCode || ( (init.charCode = chr) && chr.charCodeAt(0) ) || 0, bubbles = init.bubbles, cancelable = init.cancelable, repeat = init.repeat, locale = init.locale, view = init.view || window, args ; if (!init.which) init.which = init.keyCode; if ('initKeyEvent' in out) { out.initKeyEvent( type, bubbles, cancelable, view, ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode ); } else if (0 < initType && 'initKeyboardEvent' in out) { args = [type, bubbles, cancelable, view]; switch (initType) { case 1: args.push(key, location, ctrlKey, shiftKey, altKey, metaKey, altGraphKey); break; case 2: args.push(ctrlKey, altKey, shiftKey, metaKey, keyCode, charCode); break; case 3: args.push(key, location, ctrlKey, altKey, shiftKey, metaKey, altGraphKey); break; case 4: args.push(key, location, modifiers, repeat, locale); break; default: args.push(char, key, location, modifiers, repeat, locale); } out.initKeyboardEvent.apply(out, args); } else { out.initEvent(type, bubbles, cancelable); } for (key in out) { if (defaults.hasOwnProperty(key) && out[key] !== init[key]) { withInitValues(key, out, init); } } return out; } KeyboardEvent.prototype = $KeyboardEvent.prototype; return KeyboardEvent; }(window.KeyboardEvent || function KeyboardEvent() {})); defineProperty(window, 'KeyboardEvent', {value: o_O}); // Android 4 gotcha if (KeyboardEvent !== o_O) KeyboardEvent = o_O; } // window.MouseEvent as constructor try { new MouseEvent('_', {}); } catch (o_O) { /* jshint -W022 */ o_O = (function ($MouseEvent) { function MouseEvent(type, init) { enoughArguments(arguments.length, 'MouseEvent'); var out = document.createEvent('MouseEvent'); if (!init) init = {}; out.initMouseEvent( type, !!init.bubbles, !!init.cancelable, init.view || window, init.detail || 1, init.screenX || 0, init.screenY || 0, init.clientX || 0, init.clientY || 0, !!init.ctrlKey, !!init.altKey, !!init.shiftKey, !!init.metaKey, init.button || 0, init.relatedTarget || null ); return out; } MouseEvent.prototype = $MouseEvent.prototype; return MouseEvent; }(window.MouseEvent || function MouseEvent() {})); defineProperty(window, 'MouseEvent', {value: o_O}); // Android 4 gotcha if (MouseEvent !== o_O) MouseEvent = o_O; } if (!document.querySelectorAll('*').forEach) { (function () { function patch(what) { var querySelectorAll = what.querySelectorAll; what.querySelectorAll = function qSA(css) { var result = querySelectorAll.call(this, css); result.forEach = Array.prototype.forEach; return result; }; } patch(document); patch(Element.prototype); }()); } try { // https://drafts.csswg.org/selectors-4/#the-scope-pseudo document.querySelector(':scope *'); } catch(o_O) { (function () { var dataScope = 'data-scope-' + (Math.random() * 1e9 >>> 0); var proto = Element.prototype; var querySelector = proto.querySelector; var querySelectorAll = proto.querySelectorAll; proto.querySelector = function qS(css) { return find(this, querySelector, css); }; proto.querySelectorAll = function qSA(css) { return find(this, querySelectorAll, css); }; function find(node, method, css) { if (node.type != document.ELEMENT_NODE) return method.call(node, css); node.setAttribute(dataScope, null); var result = method.call( node, String(css).replace( /(^|,\s*)(:scope([ >]|$))/g, function ($0, $1, $2, $3) { return $1 + '[' + dataScope + ']' + ($3 || ' '); } ) ); node.removeAttribute(dataScope); return result; } }()); } }(window)); (function (global){'use strict'; // a WeakMap fallback for DOM nodes only used as key var DOMMap = global.WeakMap || (function () { var counter = 0, dispatched = false, drop = false, value ; function dispatch(key, ce, shouldDrop) { drop = shouldDrop; dispatched = false; value = undefined; key.dispatchEvent(ce); } function Handler(value) { this.value = value; } Handler.prototype.handleEvent = function handleEvent(e) { dispatched = true; if (drop) { e.currentTarget.removeEventListener(e.type, this, false); } else { value = this.value; } }; function DOMMap() { counter++; // make id clashing highly improbable this.__ce__ = new Event(('@DOMMap:' + counter) + Math.random()); } DOMMap.prototype = { 'constructor': DOMMap, 'delete': function del(key) { return dispatch(key, this.__ce__, true), dispatched; }, 'get': function get(key) { dispatch(key, this.__ce__, false); var v = value; value = undefined; return v; }, 'has': function has(key) { return dispatch(key, this.__ce__, false), dispatched; }, 'set': function set(key, value) { dispatch(key, this.__ce__, true); key.addEventListener(this.__ce__.type, new Handler(value), false); return this; }, }; return DOMMap; }()); function Dict() {} Dict.prototype = (Object.create || Object)(null); // https://dom.spec.whatwg.org/#interface-eventtarget function createEventListener(type, callback, options) { function eventListener(e) { if (eventListener.once) { e.currentTarget.removeEventListener( e.type, callback, eventListener ); eventListener.removed = true; } if (eventListener.passive) { e.preventDefault = createEventListener.preventDefault; } if (typeof eventListener.callback === 'function') { /* jshint validthis: true */ eventListener.callback.call(this, e); } else if (eventListener.callback) { eventListener.callback.handleEvent(e); } if (eventListener.passive) { delete e.preventDefault; } } eventListener.type = type; eventListener.callback = callback; eventListener.capture = !!options.capture; eventListener.passive = !!options.passive; eventListener.once = !!options.once; // currently pointless but specs say to use it, so ... eventListener.removed = false; return eventListener; } createEventListener.preventDefault = function preventDefault() {}; var Event = global.CustomEvent, dE = global.dispatchEvent, aEL = global.addEventListener, rEL = global.removeEventListener, counter = 0, increment = function () { counter++; }, indexOf = [].indexOf || function indexOf(value){ var length = this.length; while(length--) { if (this[length] === value) { break; } } return length; }, getListenerKey = function (options) { return ''.concat( options.capture ? '1' : '0', options.passive ? '1' : '0', options.once ? '1' : '0' ); }, augment ; try { aEL('_', increment, {once: true}); dE(new Event('_')); dE(new Event('_')); rEL('_', increment, {once: true}); } catch(o_O) {} if (counter !== 1) { (function () { var dm = new DOMMap(); function createAEL(aEL) { return function addEventListener(type, handler, options) { if (options && typeof options !== 'boolean') { var info = dm.get(this), key = getListenerKey(options), i, tmp, wrap ; if (!info) dm.set(this, (info = new Dict())); if (!(type in info)) info[type] = { handler: [], wrap: [] }; tmp = info[type]; i = indexOf.call(tmp.handler, handler); if (i < 0) { i = tmp.handler.push(handler) - 1; tmp.wrap[i] = (wrap = new Dict()); } else { wrap = tmp.wrap[i]; } if (!(key in wrap)) { wrap[key] = createEventListener(type, handler, options); aEL.call(this, type, wrap[key], wrap[key].capture); } } else { aEL.call(this, type, handler, options); } }; } function createREL(rEL) { return function removeEventListener(type, handler, options) { if (options && typeof options !== 'boolean') { var info = dm.get(this), key, i, tmp, wrap ; if (info && (type in info)) { tmp = info[type]; i = indexOf.call(tmp.handler, handler); if (-1 < i) { key = getListenerKey(options); wrap = tmp.wrap[i]; if (key in wrap) { rEL.call(this, type, wrap[key], wrap[key].capture); delete wrap[key]; // return if there are other wraps for (key in wrap) return; // otherwise remove all the things tmp.handler.splice(i, 1); tmp.wrap.splice(i, 1); // if there are no other handlers if (tmp.handler.length === 0) // drop the info[type] entirely delete info[type]; } } } } else { rEL.call(this, type, handler, options); } }; } augment = function (Constructor) { if (!Constructor) return; var proto = Constructor.prototype; proto.addEventListener = createAEL(proto.addEventListener); proto.removeEventListener = createREL(proto.removeEventListener); }; if (global.EventTarget) { augment(EventTarget); } else { augment(global.Text); augment(global.Element || global.HTMLElement); augment(global.HTMLDocument); augment(global.Window || {prototype:global}); augment(global.XMLHttpRequest); } }()); } }(self));