UNPKG

aurelia-pal-browser

Version:

The browser-specific implementation of Aurelia's platform abstraction layer.

621 lines (522 loc) 18.8 kB
import {initializePAL,isInitialized} from 'aurelia-pal'; export const _PLATFORM = { location: window.location, history: window.history, addEventListener(eventName: string, callback: Function, capture: boolean): void { this.global.addEventListener(eventName, callback, capture); }, removeEventListener(eventName: string, callback: Function, capture: boolean): void { this.global.removeEventListener(eventName, callback, capture); }, performance: window.performance, requestAnimationFrame(callback: Function): number { return this.global.requestAnimationFrame(callback); } }; if (typeof FEATURE_NO_IE === 'undefined') { // Fix Function#name on browsers that do not support it (IE): function test() {} // Fix: don't shorten to `!test.name` as minifiers may remove the `test` function name, // which results in `test.name === ''`, which is falsy. if (test.name === undefined) { Object.defineProperty(Function.prototype, 'name', { get: function() { let name = this.toString().match(/^\s*function\s*(\S*)\s*\(/)[1]; // For better performance only parse once, and then cache the // result through a new accessor for repeated access. Object.defineProperty(this, 'name', { value: name }); return name; } }); } } if (typeof FEATURE_NO_IE === 'undefined') { /* * classList polyfill. Forked from https://github.com/eligrey/classList.js * * Original impelementation by Eli Grey, http://eligrey.com * License: Dedicated to the public domain. * See https://github.com/eligrey/classList.js/blob/master/LICENSE.md */ // Full polyfill for browsers with no classList support // Including IE < Edge missing SVGElement.classList if (!('classList' in document.createElement('_')) || document.createElementNS && !('classList' in document.createElementNS('http://www.w3.org/2000/svg', 'g'))) { let protoProp = 'prototype'; let strTrim = String.prototype.trim; let arrIndexOf = Array.prototype.indexOf; let emptyArray = []; let DOMEx = function(type, message) { this.name = type; this.code = DOMException[type]; this.message = message; }; let checkTokenAndGetIndex = function(classList, token) { if (token === '') { throw new DOMEx('SYNTAX_ERR', 'An invalid or illegal string was specified'); } if (/\s/.test(token)) { throw new DOMEx('INVALID_CHARACTER_ERR', 'String contains an invalid character'); } return arrIndexOf.call(classList, token); }; let ClassList = function(elem) { let trimmedClasses = strTrim.call(elem.getAttribute('class') || ''); let classes = trimmedClasses ? trimmedClasses.split(/\s+/) : emptyArray; for (let i = 0, ii = classes.length; i < ii; ++i) { this.push(classes[i]); } this._updateClassName = function() { elem.setAttribute('class', this.toString()); }; }; let classListProto = ClassList[protoProp] = []; // Most DOMException implementations don't allow calling DOMException's toString() // on non-DOMExceptions. Error's toString() is sufficient here. DOMEx[protoProp] = Error[protoProp]; classListProto.item = function(i) { return this[i] || null; }; classListProto.contains = function(token) { token += ''; return checkTokenAndGetIndex(this, token) !== -1; }; classListProto.add = function() { let tokens = arguments; let i = 0; let ii = tokens.length; let token; let updated = false; do { token = tokens[i] + ''; if (checkTokenAndGetIndex(this, token) === -1) { this.push(token); updated = true; } } while (++i < ii); if (updated) { this._updateClassName(); } }; classListProto.remove = function() { let tokens = arguments; let i = 0; let ii = tokens.length; let token; let updated = false; let index; do { token = tokens[i] + ''; index = checkTokenAndGetIndex(this, token); while (index !== -1) { this.splice(index, 1); updated = true; index = checkTokenAndGetIndex(this, token); } } while (++i < ii); if (updated) { this._updateClassName(); } }; classListProto.toggle = function(token, force) { token += ''; let result = this.contains(token); let method = result ? force !== true && 'remove' : force !== false && 'add'; if (method) { this[method](token); } if (force === true || force === false) { return force; } return !result; }; classListProto.toString = function() { return this.join(' '); }; Object.defineProperty(Element.prototype, 'classList', { get: function() { return new ClassList(this); }, enumerable: true, configurable: true }); } else { // There is full or partial native classList support, so just check if we need // to normalize the add/remove and toggle APIs. let testElement = document.createElement('_'); testElement.classList.add('c1', 'c2'); // Polyfill for IE 10/11 and Firefox <26, where classList.add and // classList.remove exist but support only one argument at a time. if (!testElement.classList.contains('c2')) { let createMethod = function(method) { let original = DOMTokenList.prototype[method]; DOMTokenList.prototype[method] = function(token) { for (let i = 0, ii = arguments.length; i < ii; ++i) { token = arguments[i]; original.call(this, token); } }; }; createMethod('add'); createMethod('remove'); } testElement.classList.toggle('c3', false); // Polyfill for IE 10 and Firefox <24, where classList.toggle does not // support the second argument. if (testElement.classList.contains('c3')) { let _toggle = DOMTokenList.prototype.toggle; DOMTokenList.prototype.toggle = function(token, force) { if (1 in arguments && !this.contains(token) === !force) { return force; } return _toggle.call(this, token); }; } testElement = null; } } if (typeof FEATURE_NO_IE === 'undefined') { // performance polyfill. Copied from https://gist.github.com/paulirish/5438650 // https://gist.github.com/paulirish/5438650 // @license http://opensource.org/licenses/MIT // copyright Paul Irish 2015 if ('performance' in window === false) { window.performance = {}; } if ('now' in window.performance === false) { let nowOffset = Date.now(); if (performance.timing && performance.timing.navigationStart) { nowOffset = performance.timing.navigationStart; } window.performance.now = function now() { return Date.now() - nowOffset; }; } const startOffset = Date.now ? Date.now() : + (new Date); const _entries = []; const _marksIndex = {}; function _filterEntries(key, value) { var i = 0, n = _entries.length, result = []; for (; i < n; i++) { if (_entries[i][key] == value) { result.push(_entries[i]); } } return result; } function _clearEntries(type, name) { var i = _entries.length, entry; while (i--) { entry = _entries[i]; if (entry.entryType == type && (name === void 0 || entry.name == name)) { _entries.splice(i, 1); } } }; if (!window.performance.mark) { window.performance.mark = window.performance.webkitMark || function (name) { const mark = { name, entryType: "mark", startTime: window.performance.now(), duration: 0 }; _entries.push(mark); _marksIndex[name] = mark; }; } if (!window.performance.measure) { window.performance.measure = window.performance.webkitMeasure || function (name, startMark, endMark) { startMark = _marksIndex[startMark].startTime; endMark = _marksIndex[endMark].startTime; _entries.push({ name, entryType: "measure", startTime: startMark, duration: endMark - startMark }); }; } if (!window.performance.getEntriesByType) { window.performance.getEntriesByType = window.performance.webkitGetEntriesByType || function (type) { return _filterEntries("entryType", type); }; } if (!window.performance.getEntriesByName) { window.performance.getEntriesByName = window.performance.webkitGetEntriesByName || function (name) { return _filterEntries("name", name); }; } if (!window.performance.clearMarks) { window.performance.clearMarks = window.performance.webkitClearMarks || function (name) { _clearEntries("mark", name); }; } if (!window.performance.clearMeasures) { window.performance.clearMeasures = window.performance.webkitClearMeasures || function (name) { _clearEntries("measure", name); }; } _PLATFORM.performance = window.performance; } if (typeof FEATURE_NO_IE === 'undefined') { // References to IE 9 in this file mean the *real* IE 9 browser, not IE 11 in 9 emulation mode. // Note that in IE 9, until the F12 are actually opened window.console is undefined! let con = window.console = window.console || {}; let nop = function() {}; // console.memory is actually Chrome-only at this point, // but Aurelia does not use it so we're cutting down on "polyfills" here. // Moreover, that object is utterly useless in other browsers, as all stats would actually be 'undefined' if (!con.memory) con.memory = {}; ('assert,clear,count,debug,dir,dirxml,error,exception,group,' + 'groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd,' + 'show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn') .split(',') .forEach(m => { if (!con[m]) con[m] = nop; }); // This is really f***ed up IE 9 stuff. // You can be in a situation where console.log is an object, not a function. // And the magic voodoo below that should _not_ work (the Function.prototype.call.bind(object,...) part) // actually kicks IE 9 into converting that object into a real function that actually logs stuff. // See http://patik.com/blog/complete-cross-browser-console-log/ if (typeof con.log === 'object') { 'log,info,warn,error,assert,dir,clear,profile,profileEnd' .split(',') .forEach(function(method) { console[method] = this.bind(console[method], console); }, Function.prototype.call); } } if (typeof FEATURE_NO_IE === 'undefined') { if (!window.CustomEvent || typeof window.CustomEvent !== 'function') { let CustomEvent = function(event, params) { params = params || { bubbles: false, cancelable: false, detail: undefined }; let evt = document.createEvent('CustomEvent'); evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail); return evt; }; CustomEvent.prototype = window.Event.prototype; window.CustomEvent = CustomEvent; } } if (Element && !Element.prototype.matches) { let proto = Element.prototype; proto.matches = proto.matchesSelector || proto.mozMatchesSelector || proto.msMatchesSelector || proto.oMatchesSelector || proto.webkitMatchesSelector; } export const _FEATURE = { shadowDOM: !!HTMLElement.prototype.attachShadow, scopedCSS: 'scoped' in document.createElement('style'), htmlTemplateElement: (function() { let d = document.createElement('div'); d.innerHTML = '<template></template>'; return 'content' in d.children[0]; })(), mutationObserver: !!(window.MutationObserver || window.WebKitMutationObserver), ensureHTMLTemplateElement: t => t }; if (typeof FEATURE_NO_IE === 'undefined') { function isSVGTemplate(el) { return el.tagName === 'template' && el.namespaceURI === 'http://www.w3.org/2000/svg'; } function fixSVGTemplateElement(el) { let template = el.ownerDocument.createElement('template'); let attrs = el.attributes; let length = attrs.length; let attr; el.parentNode.insertBefore(template, el); while (length-- > 0) { attr = attrs[length]; template.setAttribute(attr.name, attr.value); el.removeAttribute(attr.name); } el.parentNode.removeChild(el); return fixHTMLTemplateElement(template); } function fixHTMLTemplateElement(template) { let content = template.content = document.createDocumentFragment(); let child; while (child = template.firstChild) { content.appendChild(child); } return template; } function fixHTMLTemplateElementRoot(template) { let content = fixHTMLTemplateElement(template).content; let childTemplates = content.querySelectorAll('template'); for (let i = 0, ii = childTemplates.length; i < ii; ++i) { let child = childTemplates[i]; if (isSVGTemplate(child)) { fixSVGTemplateElement(child); } else { fixHTMLTemplateElement(child); } } return template; } if (!_FEATURE.htmlTemplateElement) { _FEATURE.ensureHTMLTemplateElement = fixHTMLTemplateElementRoot; } } let shadowPoly = window.ShadowDOMPolyfill || null; /** * Represents the core APIs of the DOM. */ export const _DOM = { Element: Element, NodeList: NodeList, SVGElement: SVGElement, boundary: 'aurelia-dom-boundary', addEventListener(eventName: string, callback: EventListenerOrEventListenerObject, capture?: boolean): void { document.addEventListener(eventName, callback, capture); }, removeEventListener(eventName: string, callback: EventListenerOrEventListenerObject, capture?: boolean): void { document.removeEventListener(eventName, callback, capture); }, adoptNode(node: Node) { return document.adoptNode(node); }, createAttribute(name: string): Attr { return document.createAttribute(name); }, createElement(tagName: string): Element { return document.createElement(tagName); }, createTextNode(text) { return document.createTextNode(text); }, createComment(text) { return document.createComment(text); }, createDocumentFragment(): DocumentFragment { return document.createDocumentFragment(); }, createTemplateElement(): HTMLTemplateElement { let template = document.createElement('template'); return _FEATURE.ensureHTMLTemplateElement(template); }, createMutationObserver(callback: Function): MutationObserver { return new (window.MutationObserver || window.WebKitMutationObserver)(callback); }, createCustomEvent<T = any>(eventType: string, options?: CustomEventInit<T>): CustomEvent<T> { return new window.CustomEvent(eventType, options); }, dispatchEvent(evt): void { document.dispatchEvent(evt); }, getComputedStyle(element: Element) { return window.getComputedStyle(element); }, getElementById(id: string): Element { return document.getElementById(id); }, querySelector(query: string) { return document.querySelector(query); }, querySelectorAll(query: string) { return document.querySelectorAll(query); }, nextElementSibling(element: Node): Element { if (element.nextElementSibling) { return element.nextElementSibling; } do { element = element.nextSibling; } while (element && element.nodeType !== 1); return element; }, createTemplateFromMarkup(markup: string): HTMLTemplateElement { let parser = document.createElement('div'); parser.innerHTML = markup; let temp = parser.firstElementChild; if (!temp || temp.nodeName !== 'TEMPLATE') { throw new Error('Template markup must be wrapped in a <template> element e.g. <template> <!-- markup here --> </template>'); } return _FEATURE.ensureHTMLTemplateElement(temp); }, appendNode(newNode: Node, parentNode?: Node): void { (parentNode || document.body).appendChild(newNode); }, replaceNode(newNode: Node, node: Node, parentNode?: Node): void { if (node.parentNode) { node.parentNode.replaceChild(newNode, node); } else if (shadowPoly !== null) { //HACK: IE template element and shadow dom polyfills not quite right... shadowPoly.unwrap(parentNode).replaceChild( shadowPoly.unwrap(newNode), shadowPoly.unwrap(node) ); } else { //HACK: same as above parentNode.replaceChild(newNode, node); } }, removeNode(node: Node, parentNode?: Node): void { if (node.parentNode) { node.parentNode.removeChild(node); } else if (parentNode) { if (shadowPoly !== null) { //HACK: IE template element and shadow dom polyfills not quite right... shadowPoly.unwrap(parentNode).removeChild(shadowPoly.unwrap(node)); } else { //HACK: same as above parentNode.removeChild(node); } } }, injectStyles(styles: string, destination?: Element, prepend?: boolean, id?: string): Node { if (id) { let oldStyle = document.getElementById(id); if (oldStyle) { let isStyleTag = oldStyle.tagName.toLowerCase() === 'style'; if (isStyleTag) { oldStyle.innerHTML = styles; return; } throw new Error('The provided id does not indicate a style tag.'); } } let node = document.createElement('style'); node.innerHTML = styles; node.type = 'text/css'; if (id) { node.id = id; } destination = destination || document.head; if (prepend && destination.childNodes.length > 0) { destination.insertBefore(node, destination.childNodes[0]); } else { destination.appendChild(node); } return node; } }; // DOM polyfills // Actually inlined by our build because of build/paths.js but `import "m"` is not properly removed!? // import './console'; // import './custom-event'; // import './function-name'; // import './html-template-element'; // import './element-matches'; // import './class-list'; // import './performance'; /** * Initializes the PAL with the Browser-targeted implementation. */ export function initialize(): void { if (isInitialized) { return; } initializePAL((platform, feature, dom) => { Object.assign(platform, _PLATFORM); Object.assign(feature, _FEATURE); Object.assign(dom, _DOM); Object.defineProperty(dom, 'title', { get: () => document.title, set: (value) => { document.title = value; } }); Object.defineProperty(dom, 'activeElement', { get: () => document.activeElement }); Object.defineProperty(platform, 'XMLHttpRequest', { get: () => platform.global.XMLHttpRequest }); }); }