/* wax - 7.0.1 - v6.0.4-181-ga34788e */ !function (name, context, definition) { //if (typeof module !== 'undefined') module.exports = definition(name, context); //else if (typeof define === 'function' && typeof define.amd === 'object') define(definition); //else context[name] = definition(name, context); context[name] = definition(name, context); }('bean', this, function (name, context) { var win = window , old = context[name] , overOut = /over|out/ , namespaceRegex = /[^\.]*(?=\..*)\.|.*/ , nameRegex = /\..*/ , addEvent = 'addEventListener' , attachEvent = 'attachEvent' , removeEvent = 'removeEventListener' , detachEvent = 'detachEvent' , doc = document || {} , root = doc.documentElement || {} , W3C_MODEL = root[addEvent] , eventSupport = W3C_MODEL ? addEvent : attachEvent , slice = Array.prototype.slice , mouseTypeRegex = /click|mouse(?!(.*wheel|scroll))|menu|drag|drop/i , mouseWheelTypeRegex = /mouse.*(wheel|scroll)/i , textTypeRegex = /^text/i , touchTypeRegex = /^touch|^gesture/i , ONE = { one: 1 } // singleton for quick matching making add() do one() , nativeEvents = (function (hash, events, i) { for (i = 0; i < events.length; i++) hash[events[i]] = 1 return hash })({}, ( 'click dblclick mouseup mousedown contextmenu ' + // mouse buttons 'mousewheel mousemultiwheel DOMMouseScroll ' + // mouse wheel 'mouseover mouseout mousemove selectstart selectend ' + // mouse movement 'keydown keypress keyup ' + // keyboard 'orientationchange ' + // mobile 'focus blur change reset select submit ' + // form elements 'load unload beforeunload resize move DOMContentLoaded readystatechange ' + // window 'error abort scroll ' + // misc (W3C_MODEL ? // element.fireEvent('onXYZ'... is not forgiving if we try to fire an event // that doesn't actually exist, so make sure we only do these on newer browsers 'show ' + // mouse buttons 'input invalid ' + // form elements 'touchstart touchmove touchend touchcancel ' + // touch 'gesturestart gesturechange gestureend ' + // gesture 'MSPointerUp MSPointerDown MSPointerCancel MSPointerMove ' + // MS Pointer events 'MSPointerOver MSPointerOut ' + // MS Pointer events 'pointerup pointerdown pointermove pointercancel' + // MS Pointer events 'message readystatechange pageshow pagehide popstate ' + // window 'hashchange offline online ' + // window 'afterprint beforeprint ' + // printing 'dragstart dragenter dragover dragleave drag drop dragend ' + // dnd 'loadstart progress suspend emptied stalled loadmetadata ' + // media 'loadeddata canplay canplaythrough playing waiting seeking ' + // media 'seeked ended durationchange timeupdate play pause ratechange ' + // media 'volumechange cuechange ' + // media 'checking noupdate downloading cached updateready obsolete ' + // appcache '' : '') ).split(' ') ) , customEvents = (function () { function isDescendant(parent, node) { while ((node = node.parentNode) !== null) { if (node === parent) return true } return false } function check(event) { var related = event.relatedTarget if (!related) return related === null return (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related)) } return { mouseenter: { base: 'mouseover', condition: check } , mouseleave: { base: 'mouseout', condition: check } , mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' } } })() , fixEvent = (function () { var commonProps = 'altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which'.split(' ') , mouseProps = commonProps.concat('button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement'.split(' ')) , mouseWheelProps = mouseProps.concat('wheelDelta wheelDeltaX wheelDeltaY wheelDeltaZ axis'.split(' ')) // 'axis' is FF specific , keyProps = commonProps.concat('char charCode key keyCode keyIdentifier keyLocation'.split(' ')) , textProps = commonProps.concat(['data']) , touchProps = commonProps.concat('touches targetTouches changedTouches scale rotation'.split(' ')) , preventDefault = 'preventDefault' , createPreventDefault = function (event) { return function () { if (event[preventDefault]) event[preventDefault]() else event.returnValue = false } } , stopPropagation = 'stopPropagation' , createStopPropagation = function (event) { return function () { if (event[stopPropagation]) event[stopPropagation]() else event.cancelBubble = true } } , createStop = function (synEvent) { return function () { synEvent[preventDefault]() synEvent[stopPropagation]() synEvent.stopped = true } } , copyProps = function (event, result, props) { var i, p for (i = props.length; i--;) { p = props[i] if (!(p in result) && p in event) result[p] = event[p] } } return function (event, isNative) { var result = { originalEvent: event, isNative: isNative } if (!event) return result var props , type = event.type , target = event.target || event.srcElement result[preventDefault] = createPreventDefault(event) result[stopPropagation] = createStopPropagation(event) result.stop = createStop(result) result.target = target && target.nodeType === 3 ? target.parentNode : target if (isNative) { // we only need basic augmentation on custom events, the rest is too expensive if (type.indexOf('key') !== -1) { props = keyProps result.keyCode = event.which || event.keyCode } else if (mouseTypeRegex.test(type)) { props = mouseProps result.rightClick = event.which === 3 || event.button === 2 result.pos = { x: 0, y: 0 } if (event.pageX || event.pageY) { result.clientX = event.pageX result.clientY = event.pageY } else if (event.clientX || event.clientY) { result.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft result.clientY = event.clientY + doc.body.scrollTop + root.scrollTop } if (overOut.test(type)) result.relatedTarget = event.relatedTarget || event[(type === 'mouseover' ? 'from' : 'to') + 'Element'] } else if (touchTypeRegex.test(type)) { props = touchProps } else if (mouseWheelTypeRegex.test(type)) { props = mouseWheelProps } else if (textTypeRegex.test(type)) { props = textProps } copyProps(event, result, props || commonProps) } return result } })() // if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both , targetElement = function (element, isNative) { return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element } // we use one of these per listener, of any type , RegEntry = (function () { function entry(element, type, handler, original, namespaces) { this.element = element this.type = type this.handler = handler this.original = original this.namespaces = namespaces this.custom = customEvents[type] this.isNative = nativeEvents[type] && element[eventSupport] this.eventType = W3C_MODEL || this.isNative ? type : 'propertychange' this.customType = !W3C_MODEL && !this.isNative && type this.target = targetElement(element, this.isNative) this.eventSupport = this.target[eventSupport] } entry.prototype = { // given a list of namespaces, is our entry in any of them? inNamespaces: function (checkNamespaces) { var i, j if (!checkNamespaces) return true if (!this.namespaces) return false for (i = checkNamespaces.length; i--;) { for (j = this.namespaces.length; j--;) { if (checkNamespaces[i] === this.namespaces[j]) return true } } return false } // match by element, original fn (opt), handler fn (opt) , matches: function (checkElement, checkOriginal, checkHandler) { return this.element === checkElement && (!checkOriginal || this.original === checkOriginal) && (!checkHandler || this.handler === checkHandler) } } return entry })() , registry = (function () { // our map stores arrays by event type, just because it's better than storing // everything in a single array. uses '$' as a prefix for the keys for safety var map = {} // generic functional search of our registry for matching listeners, // `fn` returns false to break out of the loop , forAll = function (element, type, original, handler, fn) { if (!type || type === '*') { // search the whole registry for (var t in map) { if (t.charAt(0) === '$') forAll(element, t.substr(1), original, handler, fn) } } else { var i = 0, l, list = map['$' + type], all = element === '*' if (!list) return for (l = list.length; i < l; i++) { if (all || list[i].matches(element, original, handler)) if (!fn(list[i], list, i, type)) return } } } , has = function (element, type, original) { // we're not using forAll here simply because it's a bit slower and this // needs to be fast var i, list = map['$' + type] if (list) { for (i = list.length; i--;) { if (list[i].matches(element, original, null)) return true } } return false } , get = function (element, type, original) { var entries = [] forAll(element, type, original, null, function (entry) { return entries.push(entry) }) return entries } , put = function (entry) { (map['$' + entry.type] || (map['$' + entry.type] = [])).push(entry) return entry } , del = function (entry) { forAll(entry.element, entry.type, null, entry.handler, function (entry, list, i) { list.splice(i, 1) if (list.length === 0) delete map['$' + entry.type] return false }) } // dump all entries, used for onunload , entries = function () { var t, entries = [] for (t in map) { if (t.charAt(0) === '$') entries = entries.concat(map[t]) } return entries } return { has: has, get: get, put: put, del: del, entries: entries } })() // add and remove listeners to DOM elements , listener = W3C_MODEL ? function (element, type, fn, add) { element[add ? addEvent : removeEvent](type, fn, false) } : function (element, type, fn, add, custom) { if (custom && add && element['_on' + custom] === null) element['_on' + custom] = 0 element[add ? attachEvent : detachEvent]('on' + type, fn) } , nativeHandler = function (element, fn, args) { return function (event) { event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, true) return fn.apply(element, [event].concat(args)) } } , customHandler = function (element, fn, type, condition, args, isNative) { return function (event) { if (condition ? condition.apply(this, arguments) : W3C_MODEL ? true : event && event.propertyName === '_on' + type || !event) { if (event) event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, isNative) fn.apply(element, event && (!args || args.length === 0) ? arguments : slice.call(arguments, event ? 0 : 1).concat(args)) } } } , once = function (rm, element, type, fn, originalFn) { // wrap the handler in a handler that does a remove as well return function () { rm(element, type, originalFn) fn.apply(this, arguments) } } , removeListener = function (element, orgType, handler, namespaces) { var i, l, entry , type = (orgType && orgType.replace(nameRegex, '')) , handlers = registry.get(element, type, handler) for (i = 0, l = handlers.length; i < l; i++) { if (handlers[i].inNamespaces(namespaces)) { if ((entry = handlers[i]).eventSupport) listener(entry.target, entry.eventType, entry.handler, false, entry.type) // TODO: this is problematic, we have a registry.get() and registry.del() that // both do registry searches so we waste cycles doing this. Needs to be rolled into // a single registry.forAll(fn) that removes while finding, but the catch is that // we'll be splicing the arrays that we're iterating over. Needs extra tests to // make sure we don't screw it up. @rvagg registry.del(entry) } } } , addListener = function (element, orgType, fn, originalFn, args) { var entry , type = orgType.replace(nameRegex, '') , namespaces = orgType.replace(namespaceRegex, '').split('.') if (registry.has(element, type, fn)) return element // no dupe if (type === 'unload') fn = once(removeListener, element, type, fn, originalFn) // self clean-up if (customEvents[type]) { if (customEvents[type].condition) fn = customHandler(element, fn, type, customEvents[type].condition, true) type = customEvents[type].base || type } entry = registry.put(new RegEntry(element, type, fn, originalFn, namespaces[0] && namespaces)) entry.handler = entry.isNative ? nativeHandler(element, entry.handler, args) : customHandler(element, entry.handler, type, false, args, false) if (entry.eventSupport) listener(entry.target, entry.eventType, entry.handler, true, entry.customType) } , del = function (selector, fn, $) { return function (e) { var target, i, array = typeof selector === 'string' ? $(selector, this) : selector for (target = e.target; target && target !== this; target = target.parentNode) { for (i = array.length; i--;) { if (array[i] === target) { return fn.apply(target, arguments) } } } } } , remove = function (element, typeSpec, fn) { var k, m, type, namespaces, i , rm = removeListener , isString = typeSpec && typeof typeSpec === 'string' if (isString && typeSpec.indexOf(' ') > 0) { // remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3') typeSpec = typeSpec.split(' ') for (i = typeSpec.length; i--;) remove(element, typeSpec[i], fn) return element } type = isString && typeSpec.replace(nameRegex, '') if (type && customEvents[type]) type = customEvents[type].type if (!typeSpec || isString) { // remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3) if (namespaces = isString && typeSpec.replace(namespaceRegex, '')) namespaces = namespaces.split('.') rm(element, type, fn, namespaces) } else if (typeof typeSpec === 'function') { // remove(el, fn) rm(element, null, typeSpec) } else { // remove(el, { t1: fn1, t2, fn2 }) for (k in typeSpec) { if (typeSpec.hasOwnProperty(k)) remove(element, k, typeSpec[k]) } } return element } , add = function (element, events, fn, delfn, $) { var type, types, i, args , originalFn = fn , isDel = fn && typeof fn === 'string' if (events && !fn && typeof events === 'object') { for (type in events) { if (events.hasOwnProperty(type)) add.apply(this, [ element, type, events[type] ]) } } else { args = arguments.length > 3 ? slice.call(arguments, 3) : [] types = (isDel ? fn : events).split(' ') isDel && (fn = del(events, (originalFn = delfn), $)) && (args = slice.call(args, 1)) // special case for one() this === ONE && (fn = once(remove, element, events, fn, originalFn)) for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args) } return element } , one = function () { return add.apply(ONE, arguments) } , fireListener = W3C_MODEL ? function (isNative, type, element) { var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents') evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1) element.dispatchEvent(evt) } : function (isNative, type, element) { element = targetElement(element, isNative) // if not-native then we're using onpropertychange so we just increment a custom property isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++ } , fire = function (element, type, args) { var i, j, l, names, handlers , types = type.split(' ') for (i = types.length; i--;) { type = types[i].replace(nameRegex, '') if (names = types[i].replace(namespaceRegex, '')) names = names.split('.') if (!names && !args && element[eventSupport]) { fireListener(nativeEvents[type], type, element) } else { // non-native event, either because of a namespace, arguments or a non DOM element // iterate over all listeners and manually 'fire' handlers = registry.get(element, type) args = [false].concat(args) for (j = 0, l = handlers.length; j < l; j++) { if (handlers[j].inNamespaces(names)) handlers[j].handler.apply(element, args) } } } return element } , clone = function (element, from, type) { var i = 0 , handlers = registry.get(from, type) , l = handlers.length for (;i < l; i++) handlers[i].original && add(element, handlers[i].type, handlers[i].original) return element } , bean = { add: add , one: one , remove: remove , clone: clone , fire: fire , noConflict: function () { context[name] = old return this } } if (win[attachEvent]) { // for IE, clean up on unload to avoid leaks var cleanup = function () { var i, entries = registry.entries() for (i in entries) { if (entries[i].type && entries[i].type !== 'unload') remove(entries[i].element, entries[i].type) } win[detachEvent]('onunload', cleanup) win.CollectGarbage && win.CollectGarbage() } win[attachEvent]('onunload', cleanup) } return bean }) // Copyright Google Inc. // Licensed under the Apache Licence Version 2.0 // Autogenerated at Tue Oct 11 13:36:46 EDT 2011 // @provides html4 var html4 = {}; html4.atype = { NONE: 0, URI: 1, URI_FRAGMENT: 11, SCRIPT: 2, STYLE: 3, ID: 4, IDREF: 5, IDREFS: 6, GLOBAL_NAME: 7, LOCAL_NAME: 8, CLASSES: 9, FRAME_TARGET: 10 }; html4.ATTRIBS = { '*::class': 9, '*::dir': 0, '*::id': 4, '*::lang': 0, '*::onclick': 2, '*::ondblclick': 2, '*::onkeydown': 2, '*::onkeypress': 2, '*::onkeyup': 2, '*::onload': 2, '*::onmousedown': 2, '*::onmousemove': 2, '*::onmouseout': 2, '*::onmouseover': 2, '*::onmouseup': 2, '*::style': 3, '*::title': 0, 'a::accesskey': 0, 'a::coords': 0, 'a::href': 1, 'a::hreflang': 0, 'a::name': 7, 'a::onblur': 2, 'a::onfocus': 2, 'a::rel': 0, 'a::rev': 0, 'a::shape': 0, 'a::tabindex': 0, 'a::target': 10, 'a::type': 0, 'area::accesskey': 0, 'area::alt': 0, 'area::coords': 0, 'area::href': 1, 'area::nohref': 0, 'area::onblur': 2, 'area::onfocus': 2, 'area::shape': 0, 'area::tabindex': 0, 'area::target': 10, 'bdo::dir': 0, 'blockquote::cite': 1, 'br::clear': 0, 'button::accesskey': 0, 'button::disabled': 0, 'button::name': 8, 'button::onblur': 2, 'button::onfocus': 2, 'button::tabindex': 0, 'button::type': 0, 'button::value': 0, 'canvas::height': 0, 'canvas::width': 0, 'caption::align': 0, 'col::align': 0, 'col::char': 0, 'col::charoff': 0, 'col::span': 0, 'col::valign': 0, 'col::width': 0, 'colgroup::align': 0, 'colgroup::char': 0, 'colgroup::charoff': 0, 'colgroup::span': 0, 'colgroup::valign': 0, 'colgroup::width': 0, 'del::cite': 1, 'del::datetime': 0, 'dir::compact': 0, 'div::align': 0, 'dl::compact': 0, 'font::color': 0, 'font::face': 0, 'font::size': 0, 'form::accept': 0, 'form::action': 1, 'form::autocomplete': 0, 'form::enctype': 0, 'form::method': 0, 'form::name': 7, 'form::onreset': 2, 'form::onsubmit': 2, 'form::target': 10, 'h1::align': 0, 'h2::align': 0, 'h3::align': 0, 'h4::align': 0, 'h5::align': 0, 'h6::align': 0, 'hr::align': 0, 'hr::noshade': 0, 'hr::size': 0, 'hr::width': 0, 'iframe::align': 0, 'iframe::frameborder': 0, 'iframe::height': 0, 'iframe::marginheight': 0, 'iframe::marginwidth': 0, 'iframe::width': 0, 'img::align': 0, 'img::alt': 0, 'img::border': 0, 'img::height': 0, 'img::hspace': 0, 'img::ismap': 0, 'img::name': 7, 'img::src': 1, 'img::usemap': 11, 'img::vspace': 0, 'img::width': 0, 'input::accept': 0, 'input::accesskey': 0, 'input::align': 0, 'input::alt': 0, 'input::autocomplete': 0, 'input::checked': 0, 'input::disabled': 0, 'input::ismap': 0, 'input::maxlength': 0, 'input::name': 8, 'input::onblur': 2, 'input::onchange': 2, 'input::onfocus': 2, 'input::onselect': 2, 'input::readonly': 0, 'input::size': 0, 'input::src': 1, 'input::tabindex': 0, 'input::type': 0, 'input::usemap': 11, 'input::value': 0, 'ins::cite': 1, 'ins::datetime': 0, 'label::accesskey': 0, 'label::for': 5, 'label::onblur': 2, 'label::onfocus': 2, 'legend::accesskey': 0, 'legend::align': 0, 'li::type': 0, 'li::value': 0, 'map::name': 7, 'menu::compact': 0, 'ol::compact': 0, 'ol::start': 0, 'ol::type': 0, 'optgroup::disabled': 0, 'optgroup::label': 0, 'option::disabled': 0, 'option::label': 0, 'option::selected': 0, 'option::value': 0, 'p::align': 0, 'pre::width': 0, 'q::cite': 1, 'select::disabled': 0, 'select::multiple': 0, 'select::name': 8, 'select::onblur': 2, 'select::onchange': 2, 'select::onfocus': 2, 'select::size': 0, 'select::tabindex': 0, 'table::align': 0, 'table::bgcolor': 0, 'table::border': 0, 'table::cellpadding': 0, 'table::cellspacing': 0, 'table::frame': 0, 'table::rules': 0, 'table::summary': 0, 'table::width': 0, 'tbody::align': 0, 'tbody::char': 0, 'tbody::charoff': 0, 'tbody::valign': 0, 'td::abbr': 0, 'td::align': 0, 'td::axis': 0, 'td::bgcolor': 0, 'td::char': 0, 'td::charoff': 0, 'td::colspan': 0, 'td::headers': 6, 'td::height': 0, 'td::nowrap': 0, 'td::rowspan': 0, 'td::scope': 0, 'td::valign': 0, 'td::width': 0, 'textarea::accesskey': 0, 'textarea::cols': 0, 'textarea::disabled': 0, 'textarea::name': 8, 'textarea::onblur': 2, 'textarea::onchange': 2, 'textarea::onfocus': 2, 'textarea::onselect': 2, 'textarea::readonly': 0, 'textarea::rows': 0, 'textarea::tabindex': 0, 'tfoot::align': 0, 'tfoot::char': 0, 'tfoot::charoff': 0, 'tfoot::valign': 0, 'th::abbr': 0, 'th::align': 0, 'th::axis': 0, 'th::bgcolor': 0, 'th::char': 0, 'th::charoff': 0, 'th::colspan': 0, 'th::headers': 6, 'th::height': 0, 'th::nowrap': 0, 'th::rowspan': 0, 'th::scope': 0, 'th::valign': 0, 'th::width': 0, 'thead::align': 0, 'thead::char': 0, 'thead::charoff': 0, 'thead::valign': 0, 'tr::align': 0, 'tr::bgcolor': 0, 'tr::char': 0, 'tr::charoff': 0, 'tr::valign': 0, 'ul::compact': 0, 'ul::type': 0 }; html4.eflags = { OPTIONAL_ENDTAG: 1, EMPTY: 2, CDATA: 4, RCDATA: 8, UNSAFE: 16, FOLDABLE: 32, SCRIPT: 64, STYLE: 128 }; html4.ELEMENTS = { 'a': 0, 'abbr': 0, 'acronym': 0, 'address': 0, 'applet': 16, 'area': 2, 'b': 0, 'base': 18, 'basefont': 18, 'bdo': 0, 'big': 0, 'blockquote': 0, 'body': 49, 'br': 2, 'button': 0, 'canvas': 0, 'caption': 0, 'center': 0, 'cite': 0, 'code': 0, 'col': 2, 'colgroup': 1, 'dd': 1, 'del': 0, 'dfn': 0, 'dir': 0, 'div': 0, 'dl': 0, 'dt': 1, 'em': 0, 'fieldset': 0, 'font': 0, 'form': 0, 'frame': 18, 'frameset': 16, 'h1': 0, 'h2': 0, 'h3': 0, 'h4': 0, 'h5': 0, 'h6': 0, 'head': 49, 'hr': 2, 'html': 49, 'i': 0, 'iframe': 4, 'img': 2, 'input': 2, 'ins': 0, 'isindex': 18, 'kbd': 0, 'label': 0, 'legend': 0, 'li': 1, 'link': 18, 'map': 0, 'menu': 0, 'meta': 18, 'nobr': 0, 'noembed': 4, 'noframes': 20, 'noscript': 20, 'object': 16, 'ol': 0, 'optgroup': 0, 'option': 1, 'p': 1, 'param': 18, 'pre': 0, 'q': 0, 's': 0, 'samp': 0, 'script': 84, 'select': 0, 'small': 0, 'span': 0, 'strike': 0, 'strong': 0, 'style': 148, 'sub': 0, 'sup': 0, 'table': 0, 'tbody': 1, 'td': 1, 'textarea': 8, 'tfoot': 1, 'th': 1, 'thead': 1, 'title': 24, 'tr': 1, 'tt': 0, 'u': 0, 'ul': 0, 'var': 0 }; html4.ueffects = { NOT_LOADED: 0, SAME_DOCUMENT: 1, NEW_DOCUMENT: 2 }; html4.URIEFFECTS = { 'a::href': 2, 'area::href': 2, 'blockquote::cite': 0, 'body::background': 1, 'del::cite': 0, 'form::action': 2, 'img::src': 1, 'input::src': 1, 'ins::cite': 0, 'q::cite': 0 }; html4.ltypes = { UNSANDBOXED: 2, SANDBOXED: 1, DATA: 0 }; html4.LOADERTYPES = { 'a::href': 2, 'area::href': 2, 'blockquote::cite': 2, 'body::background': 1, 'del::cite': 2, 'form::action': 2, 'img::src': 1, 'input::src': 1, 'ins::cite': 2, 'q::cite': 2 };; // Copyright (C) 2006 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview * An HTML sanitizer that can satisfy a variety of security policies. * *

* The HTML sanitizer is built around a SAX parser and HTML element and * attributes schemas. * * @author mikesamuel@gmail.com * @requires html4 * @overrides window * @provides html, html_sanitize */ /** * @namespace */ var html = (function (html4) { var lcase; // The below may not be true on browsers in the Turkish locale. if ('script' === 'SCRIPT'.toLowerCase()) { lcase = function (s) { return s.toLowerCase(); }; } else { /** * {@updoc * $ lcase('SCRIPT') * # 'script' * $ lcase('script') * # 'script' * } */ lcase = function (s) { return s.replace( /[A-Z]/g, function (ch) { return String.fromCharCode(ch.charCodeAt(0) | 32); }); }; } var ENTITIES = { lt : '<', gt : '>', amp : '&', nbsp : '\240', quot : '"', apos : '\'' }; // Schemes on which to defer to uripolicy. Urls with other schemes are denied var WHITELISTED_SCHEMES = /^(?:https?|mailto|data)$/i; var decimalEscapeRe = /^#(\d+)$/; var hexEscapeRe = /^#x([0-9A-Fa-f]+)$/; /** * Decodes an HTML entity. * * {@updoc * $ lookupEntity('lt') * # '<' * $ lookupEntity('GT') * # '>' * $ lookupEntity('amp') * # '&' * $ lookupEntity('nbsp') * # '\xA0' * $ lookupEntity('apos') * # "'" * $ lookupEntity('quot') * # '"' * $ lookupEntity('#xa') * # '\n' * $ lookupEntity('#10') * # '\n' * $ lookupEntity('#x0a') * # '\n' * $ lookupEntity('#010') * # '\n' * $ lookupEntity('#x00A') * # '\n' * $ lookupEntity('Pi') // Known failure * # '\u03A0' * $ lookupEntity('pi') // Known failure * # '\u03C0' * } * * @param name the content between the '&' and the ';'. * @return a single unicode code-point as a string. */ function lookupEntity(name) { name = lcase(name); // TODO: π is different from Π if (ENTITIES.hasOwnProperty(name)) { return ENTITIES[name]; } var m = name.match(decimalEscapeRe); if (m) { return String.fromCharCode(parseInt(m[1], 10)); } else if (!!(m = name.match(hexEscapeRe))) { return String.fromCharCode(parseInt(m[1], 16)); } return ''; } function decodeOneEntity(_, name) { return lookupEntity(name); } var nulRe = /\0/g; function stripNULs(s) { return s.replace(nulRe, ''); } var entityRe = /&(#\d+|#x[0-9A-Fa-f]+|\w+);/g; /** * The plain text of a chunk of HTML CDATA which possibly containing. * * {@updoc * $ unescapeEntities('') * # '' * $ unescapeEntities('hello World!') * # 'hello World!' * $ unescapeEntities('1 < 2 && 4 > 3 ') * # '1 < 2 && 4 > 3\n' * $ unescapeEntities('<< <- unfinished entity>') * # '<< <- unfinished entity>' * $ unescapeEntities('/foo?bar=baz©=true') // & often unescaped in URLS * # '/foo?bar=baz©=true' * $ unescapeEntities('pi=ππ, Pi=Π\u03A0') // FIXME: known failure * # 'pi=\u03C0\u03c0, Pi=\u03A0\u03A0' * } * * @param s a chunk of HTML CDATA. It must not start or end inside an HTML * entity. */ function unescapeEntities(s) { return s.replace(entityRe, decodeOneEntity); } var ampRe = /&/g; var looseAmpRe = /&([^a-z#]|#(?:[^0-9x]|x(?:[^0-9a-f]|$)|$)|$)/gi; var ltRe = //g; var quotRe = /\"/g; var eqRe = /\=/g; // Backslash required on JScript.net /** * Escapes HTML special characters in attribute values as HTML entities. * * {@updoc * $ escapeAttrib('') * # '' * $ escapeAttrib('"<<&==&>>"') // Do not just escape the first occurrence. * # '"<<&==&>>"' * $ escapeAttrib('Hello !') * # 'Hello <World>!' * } */ function escapeAttrib(s) { // Escaping '=' defangs many UTF-7 and SGML short-tag attacks. return s.replace(ampRe, '&').replace(ltRe, '<').replace(gtRe, '>') .replace(quotRe, '"').replace(eqRe, '='); } /** * Escape entities in RCDATA that can be escaped without changing the meaning. * {@updoc * $ normalizeRCData('1 < 2 && 3 > 4 && 5 < 7&8') * # '1 < 2 && 3 > 4 && 5 < 7&8' * } */ function normalizeRCData(rcdata) { return rcdata .replace(looseAmpRe, '&$1') .replace(ltRe, '<') .replace(gtRe, '>'); } // TODO(mikesamuel): validate sanitizer regexs against the HTML5 grammar at // http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construction.html /** token definitions. */ var INSIDE_TAG_TOKEN = new RegExp( // Don't capture space. '^\\s*(?:' // Capture an attribute name in group 1, and value in group 3. // We capture the fact that there was an attribute in group 2, since // interpreters are inconsistent in whether a group that matches nothing // is null, undefined, or the empty string. + ('(?:' + '([a-z][a-z-]*)' // attribute name + ('(' // optionally followed + '\\s*=\\s*' + ('(' // A double quoted string. + '\"[^\"]*\"' // A single quoted string. + '|\'[^\']*\'' // The positive lookahead is used to make sure that in // , the value for bar is blank, not "baz=boo". + '|(?=[a-z][a-z-]*\\s*=)' // An unquoted value that is not an attribute name. // We know it is not an attribute name because the previous // zero-width match would've eliminated that possibility. + '|[^>\"\'\\s]*' + ')' ) + ')' ) + '?' + ')' ) // End of tag captured in group 3. + '|(\/?>)' // Don't capture cruft + '|[\\s\\S][^a-z\\s>]*)', 'i'); var OUTSIDE_TAG_TOKEN = new RegExp( '^(?:' // Entity captured in group 1. + '&(\\#[0-9]+|\\#[x][0-9a-f]+|\\w+);' // Comment, doctypes, and processing instructions not captured. + '|<\!--[\\s\\S]*?--\>|]*>|<\\?[^>*]*>' // '/' captured in group 2 for close tags, and name captured in group 3. + '|<(\/)?([a-z][a-z0-9]*)' // Text captured in group 4. + '|([^<&>]+)' // Cruft captured in group 5. + '|([<&>]))', 'i'); /** * Given a SAX-like event handler, produce a function that feeds those * events and a parameter to the event handler. * * The event handler has the form:{@code * { * // Name is an upper-case HTML tag name. Attribs is an array of * // alternating upper-case attribute names, and attribute values. The * // attribs array is reused by the parser. Param is the value passed to * // the saxParser. * startTag: function (name, attribs, param) { ... }, * endTag: function (name, param) { ... }, * pcdata: function (text, param) { ... }, * rcdata: function (text, param) { ... }, * cdata: function (text, param) { ... }, * startDoc: function (param) { ... }, * endDoc: function (param) { ... } * }} * * @param {Object} handler a record containing event handlers. * @return {Function} that takes a chunk of html and a parameter. * The parameter is passed on to the handler methods. */ function makeSaxParser(handler) { return function parse(htmlText, param) { htmlText = String(htmlText); var htmlLower = null; var inTag = false; // True iff we're currently processing a tag. var attribs = []; // Accumulates attribute names and values. var tagName = void 0; // The name of the tag currently being processed. var eflags = void 0; // The element flags for the current tag. var openTag = void 0; // True if the current tag is an open tag. if (handler.startDoc) { handler.startDoc(param); } while (htmlText) { var m = htmlText.match(inTag ? INSIDE_TAG_TOKEN : OUTSIDE_TAG_TOKEN); htmlText = htmlText.substring(m[0].length); if (inTag) { if (m[1]) { // attribute // setAttribute with uppercase names doesn't work on IE6. var attribName = lcase(m[1]); var decodedValue; if (m[2]) { var encodedValue = m[3]; switch (encodedValue.charCodeAt(0)) { // Strip quotes case 34: case 39: encodedValue = encodedValue.substring( 1, encodedValue.length - 1); break; } decodedValue = unescapeEntities(stripNULs(encodedValue)); } else { // Use name as value for valueless attribs, so // // gets attributes ['type', 'checkbox', 'checked', 'checked'] decodedValue = attribName; } attribs.push(attribName, decodedValue); } else if (m[4]) { if (eflags !== void 0) { // False if not in whitelist. if (openTag) { if (handler.startTag) { handler.startTag(tagName, attribs, param); } } else { if (handler.endTag) { handler.endTag(tagName, param); } } } if (openTag && (eflags & (html4.eflags.CDATA | html4.eflags.RCDATA))) { if (htmlLower === null) { htmlLower = lcase(htmlText); } else { htmlLower = htmlLower.substring( htmlLower.length - htmlText.length); } var dataEnd = htmlLower.indexOf('' ? '>' : '&', param); } } } } if (handler.endDoc) { handler.endDoc(param); } }; } /** * Returns a function that strips unsafe tags and attributes from html. * @param {Function} sanitizeAttributes * maps from (tagName, attribs[]) to null or a sanitized attribute array. * The attribs array can be arbitrarily modified, but the same array * instance is reused, so should not be held. * @return {Function} from html to sanitized html */ function makeHtmlSanitizer(sanitizeAttributes) { var stack; var ignoring; return makeSaxParser({ startDoc: function (_) { stack = []; ignoring = false; }, startTag: function (tagName, attribs, out) { if (ignoring) { return; } if (!html4.ELEMENTS.hasOwnProperty(tagName)) { return; } var eflags = html4.ELEMENTS[tagName]; if (eflags & html4.eflags.FOLDABLE) { return; } else if (eflags & html4.eflags.UNSAFE) { ignoring = !(eflags & html4.eflags.EMPTY); return; } attribs = sanitizeAttributes(tagName, attribs); // TODO(mikesamuel): relying on sanitizeAttributes not to // insert unsafe attribute names. if (attribs) { if (!(eflags & html4.eflags.EMPTY)) { stack.push(tagName); } out.push('<', tagName); for (var i = 0, n = attribs.length; i < n; i += 2) { var attribName = attribs[i], value = attribs[i + 1]; if (value !== null && value !== void 0) { out.push(' ', attribName, '="', escapeAttrib(value), '"'); } } out.push('>'); } }, endTag: function (tagName, out) { if (ignoring) { ignoring = false; return; } if (!html4.ELEMENTS.hasOwnProperty(tagName)) { return; } var eflags = html4.ELEMENTS[tagName]; if (!(eflags & (html4.eflags.UNSAFE | html4.eflags.EMPTY | html4.eflags.FOLDABLE))) { var index; if (eflags & html4.eflags.OPTIONAL_ENDTAG) { for (index = stack.length; --index >= 0;) { var stackEl = stack[index]; if (stackEl === tagName) { break; } if (!(html4.ELEMENTS[stackEl] & html4.eflags.OPTIONAL_ENDTAG)) { // Don't pop non optional end tags looking for a match. return; } } } else { for (index = stack.length; --index >= 0;) { if (stack[index] === tagName) { break; } } } if (index < 0) { return; } // Not opened. for (var i = stack.length; --i > index;) { var stackEl = stack[i]; if (!(html4.ELEMENTS[stackEl] & html4.eflags.OPTIONAL_ENDTAG)) { out.push(''); } } stack.length = index; out.push(''); } }, pcdata: function (text, out) { if (!ignoring) { out.push(text); } }, rcdata: function (text, out) { if (!ignoring) { out.push(text); } }, cdata: function (text, out) { if (!ignoring) { out.push(text); } }, endDoc: function (out) { for (var i = stack.length; --i >= 0;) { out.push(''); } stack.length = 0; } }); } // From RFC3986 var URI_SCHEME_RE = new RegExp( "^" + "(?:" + "([^:\/?#]+)" + // scheme ":)?" ); /** * Strips unsafe tags and attributes from html. * @param {string} htmlText to sanitize * @param {Function} opt_uriPolicy -- a transform to apply to uri/url * attribute values. If no opt_uriPolicy is provided, no uris * are allowed ie. the default uriPolicy rewrites all uris to null * @param {Function} opt_nmTokenPolicy : string -> string? -- a transform to * apply to names, ids, and classes. If no opt_nmTokenPolicy is provided, * all names, ids and classes are passed through ie. the default * nmTokenPolicy is an identity transform * @return {string} html */ function sanitize(htmlText, opt_uriPolicy, opt_nmTokenPolicy) { var out = []; makeHtmlSanitizer( function sanitizeAttribs(tagName, attribs) { for (var i = 0; i < attribs.length; i += 2) { var attribName = attribs[i]; var value = attribs[i + 1]; var atype = null, attribKey; if ((attribKey = tagName + '::' + attribName, html4.ATTRIBS.hasOwnProperty(attribKey)) || (attribKey = '*::' + attribName, html4.ATTRIBS.hasOwnProperty(attribKey))) { atype = html4.ATTRIBS[attribKey]; } if (atype !== null) { switch (atype) { case html4.atype.NONE: break; case html4.atype.SCRIPT: case html4.atype.STYLE: value = null; break; case html4.atype.ID: case html4.atype.IDREF: case html4.atype.IDREFS: case html4.atype.GLOBAL_NAME: case html4.atype.LOCAL_NAME: case html4.atype.CLASSES: value = opt_nmTokenPolicy ? opt_nmTokenPolicy(value) : value; break; case html4.atype.URI: var parsedUri = ('' + value).match(URI_SCHEME_RE); if (!parsedUri) { value = null; } else if (!parsedUri[1] || WHITELISTED_SCHEMES.test(parsedUri[1])) { value = opt_uriPolicy && opt_uriPolicy(value); } else { value = null; } break; case html4.atype.URI_FRAGMENT: if (value && '#' === value.charAt(0)) { value = opt_nmTokenPolicy ? opt_nmTokenPolicy(value) : value; if (value) { value = '#' + value; } } else { value = null; } break; default: value = null; break; } } else { value = null; } attribs[i + 1] = value; } return attribs; })(htmlText, out); return out.join(''); } return { escapeAttrib: escapeAttrib, makeHtmlSanitizer: makeHtmlSanitizer, makeSaxParser: makeSaxParser, normalizeRCData: normalizeRCData, sanitize: sanitize, unescapeEntities: unescapeEntities }; })(html4); var html_sanitize = html.sanitize; // Exports for closure compiler. Note this file is also cajoled // for domado and run in an environment without 'window' if (typeof window !== 'undefined') { window['html'] = html; window['html_sanitize'] = html_sanitize; } // Loosen restrictions of Caja's // html-sanitizer to allow for styling html4.ATTRIBS['*::style'] = 0; html4.ELEMENTS['style'] = 0; html4.ATTRIBS['a::target'] = 0; html4.ELEMENTS['video'] = 0; html4.ATTRIBS['video::src'] = 0; html4.ATTRIBS['video::poster'] = 0; html4.ATTRIBS['video::controls'] = 0; html4.ELEMENTS['audio'] = 0; html4.ATTRIBS['audio::src'] = 0; html4.ATTRIBS['video::autoplay'] = 0; html4.ATTRIBS['video::controls'] = 0; /*! * Reqwest! A general purpose XHR connection manager * (c) Dustin Diaz 2012 * https://github.com/ded/reqwest * license MIT */ ;(function (name, context, definition) { context[name] = definition() })('reqwest', this, function () { var win = window , doc = document , twoHundo = /^20\d$/ , byTag = 'getElementsByTagName' , readyState = 'readyState' , contentType = 'Content-Type' , requestedWith = 'X-Requested-With' , head = doc[byTag]('head')[0] , uniqid = 0 , callbackPrefix = 'reqwest_' + (+new Date()) , lastValue // data stored by the most recent JSONP callback , xmlHttpRequest = 'XMLHttpRequest' , noop = function () {} , isArray = typeof Array.isArray == 'function' ? Array.isArray : function (a) { return a instanceof Array } , defaultHeaders = { contentType: 'application/x-www-form-urlencoded' , requestedWith: xmlHttpRequest , accept: { '*': 'text/javascript, text/html, application/xml, text/xml, */*' , xml: 'application/xml, text/xml' , html: 'text/html' , text: 'text/plain' , json: 'application/json, text/javascript' , js: 'application/javascript, text/javascript' } } , xhr = win[xmlHttpRequest] ? function () { return new XMLHttpRequest() } : function () { return new ActiveXObject('Microsoft.XMLHTTP') } function handleReadyState (r, success, error) { return function () { // use _aborted to mitigate against IE err c00c023f // (can't read props on aborted request objects) if (r._aborted) return error(r.request) if (r.request && r.request[readyState] == 4) { r.request.onreadystatechange = noop if (twoHundo.test(r.request.status)) success(r.request) else error(r.request) } } } function setHeaders (http, o) { var headers = o.headers || {} , h headers.Accept = headers.Accept || defaultHeaders.accept[o.type] || defaultHeaders.accept['*'] // breaks cross-origin requests with legacy browsers if (!o.crossOrigin && !headers[requestedWith]) headers[requestedWith] = defaultHeaders.requestedWith if (!headers[contentType]) headers[contentType] = o.contentType || defaultHeaders.contentType for (h in headers) headers.hasOwnProperty(h) && http.setRequestHeader(h, headers[h]) } function setCredentials (http, o) { if (typeof o.withCredentials !== 'undefined' && typeof http.withCredentials !== 'undefined') { http.withCredentials = !!o.withCredentials } } function generalCallback (data) { lastValue = data } function urlappend (url, s) { return url + (/\?/.test(url) ? '&' : '?') + s } function handleJsonp (o, fn, err, url) { var reqId = uniqid++ , cbkey = o.jsonpCallback || 'callback' // the 'callback' key , cbval = o.jsonpCallbackName || reqwest.getcallbackPrefix(reqId) // , cbval = o.jsonpCallbackName || ('reqwest_' + reqId) // the 'callback' value , cbreg = new RegExp('((^|\\?|&)' + cbkey + ')=([^&]+)') , match = url.match(cbreg) , script = doc.createElement('script') , loaded = 0 , isIE10 = navigator.userAgent.indexOf('MSIE 10.0') !== -1; if (match) { if (match[3] === '?') { url = url.replace(cbreg, '$1=' + cbval) // wildcard callback func name } else { cbval = match[3] // provided callback func name } } else { url = urlappend(url, cbkey + '=' + cbval) // no callback details, add 'em } win[cbval] = generalCallback script.type = 'text/javascript' if (typeof script.onreadystatechange !== 'undefined' && !isIE10) { // need this for IE due to out-of-order onreadystatechange(), binding script // execution to an event listener gives us control over when the script // is executed. See http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html // // if this hack is used in IE10 jsonp callback are never called script.event = 'onclick' script.htmlFor = script.id = '_reqwest_' + reqId } script.onload = script.onreadystatechange = function () { if ((script[readyState] && script[readyState] !== 'complete' && script[readyState] !== 'loaded') || loaded) { return false } script.onload = script.onreadystatechange = null script.onclick && script.onclick() // Call the user callback with the last value stored and clean up values and scripts. o.success && o.success(lastValue) lastValue = undefined head.removeChild(script) loaded = 1 } script.src = url script.async = true // Add the script to the DOM head head.appendChild(script) // Enable JSONP timeout return { abort: function () { script.onload = script.onreadystatechange = null o.error && o.error({}, 'Request is aborted: timeout', {}) lastValue = undefined head.removeChild(script) loaded = 1 } } } function getRequest (fn, err) { var o = this.o , method = (o.method || 'GET').toUpperCase() , url = typeof o === 'string' ? o : o.url // convert non-string objects to query-string form unless o.processData is false , data = (o.processData !== false && o.data && typeof o.data !== 'string') ? reqwest.toQueryString(o.data) : (o.data || null) , http // if we're working on a GET request and we have data then we should append // query string to end of URL and not post data if ((o.type == 'jsonp' || method == 'GET') && data) { url = urlappend(url, data) data = null } if (o.type == 'jsonp') return handleJsonp(o, fn, err, url) http = xhr() http.open(method, url, true) setHeaders(http, o) setCredentials(http, o) http.onreadystatechange = handleReadyState(this, fn, err) o.before && o.before(http) http.send(data) return http } function Reqwest (o, fn) { this.o = o this.fn = fn init.apply(this, arguments) } function setType (url) { var m = url.match(/\.(json|jsonp|html|xml)(\?|$)/) return m ? m[1] : 'js' } function init (o, fn) { this.url = typeof o == 'string' ? o : o.url this.timeout = null // whether request has been fulfilled for purpose // of tracking the Promises this._fulfilled = false // success handlers this._fulfillmentHandlers = [] // error handlers this._errorHandlers = [] // complete (both success and fail) handlers this._completeHandlers = [] this._erred = false this._responseArgs = {} var self = this , type = o.type || setType(this.url) fn = fn || function () {} if (o.timeout) { this.timeout = setTimeout(function () { self.abort() }, o.timeout) } if (o.success) { this._fulfillmentHandlers.push(function () { o.success.apply(o, arguments) }) } if (o.error) { this._errorHandlers.push(function () { o.error.apply(o, arguments) }) } if (o.complete) { this._completeHandlers.push(function () { o.complete.apply(o, arguments) }) } function complete (resp) { o.timeout && clearTimeout(self.timeout) self.timeout = null while (self._completeHandlers.length > 0) { self._completeHandlers.shift()(resp) } } function success (resp) { var r = resp.responseText if (r) { switch (type) { case 'json': try { resp = win.JSON ? win.JSON.parse(r) : eval('(' + r + ')') } catch (err) { return error(resp, 'Could not parse JSON in response', err) } break case 'js': resp = eval(r) break case 'html': resp = r break case 'xml': resp = resp.responseXML && resp.responseXML.parseError // IE trololo && resp.responseXML.parseError.errorCode && resp.responseXML.parseError.reason ? null : resp.responseXML break } } self._responseArgs.resp = resp self._fulfilled = true fn(resp) while (self._fulfillmentHandlers.length > 0) { self._fulfillmentHandlers.shift()(resp) } complete(resp) } function error (resp, msg, t) { self._responseArgs.resp = resp self._responseArgs.msg = msg self._responseArgs.t = t self._erred = true while (self._errorHandlers.length > 0) { self._errorHandlers.shift()(resp, msg, t) } complete(resp) } this.request = getRequest.call(this, success, error) } Reqwest.prototype = { abort: function () { this._aborted = true this.request.abort() } , retry: function () { init.call(this, this.o, this.fn) } /** * Small deviation from the Promises A CommonJs specification * http://wiki.commonjs.org/wiki/Promises/A */ /** * `then` will execute upon successful requests */ , then: function (success, fail) { if (this._fulfilled) { success(this._responseArgs.resp) } else if (this._erred) { fail(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) } else { this._fulfillmentHandlers.push(success) this._errorHandlers.push(fail) } return this } /** * `always` will execute whether the request succeeds or fails */ , always: function (fn) { if (this._fulfilled || this._erred) { fn(this._responseArgs.resp) } else { this._completeHandlers.push(fn) } return this } /** * `fail` will execute when the request fails */ , fail: function (fn) { if (this._erred) { fn(this._responseArgs.resp, this._responseArgs.msg, this._responseArgs.t) } else { this._errorHandlers.push(fn) } return this } } function reqwest (o, fn) { return new Reqwest(o, fn) } // normalize newline variants according to spec -> CRLF function normalize (s) { return s ? s.replace(/\r?\n/g, '\r\n') : '' } function serial (el, cb) { var n = el.name , t = el.tagName.toLowerCase() , optCb = function (o) { // IE gives value="" even where there is no value attribute // 'specified' ref: http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-862529273 if (o && !o.disabled) cb(n, normalize(o.attributes.value && o.attributes.value.specified ? o.value : o.text)) } , ch, ra, val, i // don't serialize elements that are disabled or without a name if (el.disabled || !n) return switch (t) { case 'input': if (!/reset|button|image|file/i.test(el.type)) { ch = /checkbox/i.test(el.type) ra = /radio/i.test(el.type) val = el.value // WebKit gives us "" instead of "on" if a checkbox has no value, so correct it here ;(!(ch || ra) || el.checked) && cb(n, normalize(ch && val === '' ? 'on' : val)) } break case 'textarea': cb(n, normalize(el.value)) break case 'select': if (el.type.toLowerCase() === 'select-one') { optCb(el.selectedIndex >= 0 ? el.options[el.selectedIndex] : null) } else { for (i = 0; el.length && i < el.length; i++) { el.options[i].selected && optCb(el.options[i]) } } break } } // collect up all form elements found from the passed argument elements all // the way down to child elements; pass a '

' or form fields. // called with 'this'=callback to use for serial() on each element function eachFormElement () { var cb = this , e, i , serializeSubtags = function (e, tags) { var i, j, fa for (i = 0; i < tags.length; i++) { fa = e[byTag](tags[i]) for (j = 0; j < fa.length; j++) serial(fa[j], cb) } } for (i = 0; i < arguments.length; i++) { e = arguments[i] if (/input|select|textarea/i.test(e.tagName)) serial(e, cb) serializeSubtags(e, [ 'input', 'select', 'textarea' ]) } } // standard query string style serialization function serializeQueryString () { return reqwest.toQueryString(reqwest.serializeArray.apply(null, arguments)) } // { 'name': 'value', ... } style serialization function serializeHash () { var hash = {} eachFormElement.apply(function (name, value) { if (name in hash) { hash[name] && !isArray(hash[name]) && (hash[name] = [hash[name]]) hash[name].push(value) } else hash[name] = value }, arguments) return hash } // [ { name: 'name', value: 'value' }, ... ] style serialization reqwest.serializeArray = function () { var arr = [] eachFormElement.apply(function (name, value) { arr.push({name: name, value: value}) }, arguments) return arr } reqwest.serialize = function () { if (arguments.length === 0) return '' var opt, fn , args = Array.prototype.slice.call(arguments, 0) opt = args.pop() opt && opt.nodeType && args.push(opt) && (opt = null) opt && (opt = opt.type) if (opt == 'map') fn = serializeHash else if (opt == 'array') fn = reqwest.serializeArray else fn = serializeQueryString return fn.apply(null, args) } reqwest.toQueryString = function (o) { var qs = '', i , enc = encodeURIComponent , push = function (k, v) { qs += enc(k) + '=' + enc(v) + '&' } , k, v if (isArray(o)) { for (i = 0; o && i < o.length; i++) push(o[i].name, o[i].value) } else { for (k in o) { if (!Object.hasOwnProperty.call(o, k)) continue v = o[k] if (isArray(v)) { for (i = 0; i < v.length; i++) push(k, v[i]) } else push(k, o[k]) } } // spaces should be + according to spec return qs.replace(/&$/, '').replace(/%20/g, '+') } reqwest.getcallbackPrefix = function () { return callbackPrefix } // jQuery and Zepto compatibility, differences can be remapped here so you can call // .ajax.compat(options, callback) reqwest.compat = function (o, fn) { if (o) { o.type && (o.method = o.type) && delete o.type o.dataType && (o.type = o.dataType) o.jsonpCallback && (o.jsonpCallbackName = o.jsonpCallback) && delete o.jsonpCallback o.jsonp && (o.jsonpCallback = o.jsonp) } return new Reqwest(o, fn) } return reqwest }) ;wax = wax || {}; // Attribution // ----------- wax.attribution = function() { var a = {}; var container = document.createElement('div'); container.className = 'map-attribution'; a.content = function(x) { if (typeof x === 'undefined') return container.innerHTML; container.innerHTML = wax.u.sanitize(x); return this; }; a.element = function() { return container; }; a.init = function() { return this; }; return a; }; wax = wax || {}; // Attribution // ----------- wax.bwdetect = function(options, callback) { var detector = {}, threshold = options.threshold || 400, // test image: 30.29KB testImage = 'http://a.tiles.mapbox.com/mapbox/1.0.0/blue-marble-topo-bathy-jul/0/0/0.png?preventcache=' + (+new Date()), // High-bandwidth assumed // 1: high bandwidth (.png, .jpg) // 0: low bandwidth (.png128, .jpg70) bw = 1, // Alternative versions auto = options.auto === undefined ? true : options.auto; function bwTest() { wax.bw = -1; var im = new Image(); im.src = testImage; var first = true; var timeout = setTimeout(function() { if (first && wax.bw == -1) { detector.bw(0); first = false; } }, threshold); im.onload = function() { if (first && wax.bw == -1) { clearTimeout(timeout); detector.bw(1); first = false; } }; } detector.bw = function(x) { if (!arguments.length) return bw; var oldBw = bw; if (wax.bwlisteners && wax.bwlisteners.length) (function () { listeners = wax.bwlisteners; wax.bwlisteners = []; for (i = 0; i < listeners; i++) { listeners[i](x); } })(); wax.bw = x; if (bw != (bw = x)) callback(x); }; detector.add = function() { if (auto) bwTest(); return this; }; if (wax.bw == -1) { wax.bwlisteners = wax.bwlisteners || []; wax.bwlisteners.push(detector.bw); } else if (wax.bw !== undefined) { detector.bw(wax.bw); } else { detector.add(); } return detector; }; // Formatter // --------- // // This code is no longer the recommended code path for Wax - // see `template.js`, a safe implementation of Mustache templates. wax.formatter = function(x) { var formatter = {}, f; // Prevent against just any input being used. if (x && typeof x === 'string') { try { // Ugly, dangerous use of eval. eval('f = ' + x); } catch (e) { if (console) console.log(e); } } else if (x && typeof x === 'function') { f = x; } else { f = function() {}; } // Wrap the given formatter function in order to // catch exceptions that it may throw. formatter.format = function(options, data) { try { return wax.u.sanitize(f(options, data)); } catch (e) { if (console) console.log(e); } }; return formatter; }; // GridInstance // ------------ // GridInstances are queryable, fully-formed // objects for acquiring features from events. // // This code ignores format of 1.1-1.2 wax.gi = function(grid_tile, options) { options = options || {}; // resolution is the grid-elements-per-pixel ratio of gridded data. // The size of a tile element. For now we expect tiles to be squares. var instance = {}, resolution = options.resolution || 4, tileSize = options.tileSize || 256; // Resolve the UTF-8 encoding stored in grids to simple // number values. // See the [utfgrid spec](https://github.com/mapbox/utfgrid-spec) // for details. function resolveCode(key) { if (key >= 93) key--; if (key >= 35) key--; key -= 32; return key; } instance.grid_tile = function() { return grid_tile; }; instance.getKey = function(x, y) { if (!(grid_tile && grid_tile.grid)) return; if ((y < 0) || (x < 0)) return; if ((Math.floor(y) >= tileSize) || (Math.floor(x) >= tileSize)) return; // Find the key in the grid. The above calls should ensure that // the grid's array is large enough to make this work. return resolveCode(grid_tile.grid[ Math.floor((y) / resolution) ].charCodeAt( Math.floor((x) / resolution) )); }; // Lower-level than tileFeature - has nothing to do // with the DOM. Takes a px offset from 0, 0 of a grid. instance.gridFeature = function(x, y) { // Find the key in the grid. The above calls should ensure that // the grid's array is large enough to make this work. var key = this.getKey(x, y), keys = grid_tile.keys; if (keys && keys[key] && grid_tile.data[keys[key]]) { return grid_tile.data[keys[key]]; } }; // Get a feature: // * `x` and `y`: the screen coordinates of an event // * `tile_element`: a DOM element of a tile, from which we can get an offset. instance.tileFeature = function(x, y, tile_element) { if (!grid_tile) return; // IE problem here - though recoverable, for whatever reason var offset = wax.u.offset(tile_element); feature = this.gridFeature(x - offset.left, y - offset.top); return feature; }; return instance; }; // GridManager // ----------- // Generally one GridManager will be used per map. // // It takes one options object, which current accepts a single option: // `resolution` determines the number of pixels per grid element in the grid. // The default is 4. wax.gm = function() { var resolution = 4, grid_tiles = {}, manager = {}, tilejson, formatter; var gridUrl = function(url) { if (url) { return url.replace(/(\.png|\.jpg|\.jpeg)(\d*)/, '.grid.json'); } }; function templatedGridUrl(template) { if (typeof template === 'string') template = [template]; return function templatedGridFinder(url) { if (!url) return; var rx = new RegExp(manager.tileRegexp()) var xyz = rx.exec(url); if (!xyz) return; return template[parseInt(xyz[2], 10) % template.length] .replace(/\{z\}/g, xyz[1]) .replace(/\{x\}/g, xyz[2]) .replace(/\{y\}/g, xyz[3]); }; } // return the regexp to catch the tile number given the url manager.tileRegexp = function() { var tileTemplate = tilejson.tiles[0]; // remove params var p = tileTemplate.indexOf('?'); if(p !== -1) { tileTemplate = tileTemplate.substr(0, p); } // remove from the url all the special characters // replacing them by a dot (dont mind the character) tileTemplate = tileTemplate. replace(/[\(\)\?\$\*\+\^]/g,'.') // the browser removes the port in the case it matchs with // the default port of the protocol if(tileTemplate.indexOf('https') === 0) { tileTemplate = tileTemplate.replace(':443', '[:0-9]*') } else if(tileTemplate.indexOf('http') === 0) { tileTemplate = tileTemplate.replace(':80', '[:0-9]*') } var r = ''; if(tilejson.tiles.length > 1) { var t0 = tilejson.tiles[0]; var t1 = tilejson.tiles[1]; //search characters where differs for(var i = 0; i < t0.length; ++i) { if(t0.charAt(i) != t1.charAt(i)) { r += '.'; } else { r += tileTemplate.charAt(i) || ''; } } } else { r = tileTemplate; } // replace the first {x}{y}{z} by (\\d+) return r .replace(/\{x\}/,'(\\d+)') .replace(/\{y\}/,'(\\d+)') .replace(/\{z\}/,'(\\d+)') } manager.formatter = function(x) { if (!arguments.length) return formatter; formatter = wax.formatter(x); return manager; }; manager.template = function(x) { if (!arguments.length) return formatter; formatter = wax.template(x); return manager; }; manager.gridUrl = function(x) { // Getter-setter if (!arguments.length) return gridUrl; // Handle tilesets that don't support grids if (!x) { gridUrl = function() { return null; }; } else { gridUrl = typeof x === 'function' ? x : templatedGridUrl(x); } return manager; }; manager.getGrid = function(url, callback) { var gurl = gridUrl(url); if (!formatter || !gurl) return callback(null, null); wax.request.get(gurl, function(err, t) { if (err) return callback(err, null); callback(null, wax.gi(t, { formatter: formatter, resolution: resolution })); }); return manager; }; manager.tilejson = function(x) { if (!arguments.length) return tilejson; // prefer templates over formatters if (x.template) { manager.template(x.template); } else if (x.formatter) { manager.formatter(x.formatter); } else { // In this case, we cannot support grids formatter = undefined; } manager.gridUrl(x.grids); if (x.resolution) resolution = x.resolution; tilejson = x; return manager; }; return manager; }; wax = wax || {}; // Hash // ---- wax.hash = function(options) { options = options || {}; var s0, // old hash hash = {}, lat = 90 - 1e-8; // allowable latitude range function getState() { return location.hash.substring(1); } function pushState(state) { var l = window.location; l.replace(l.toString().replace((l.hash || /$/), '#' + state)); } function parseHash(s) { var args = s.split('/'); for (var i = 0; i < args.length; i++) { args[i] = Number(args[i]); if (isNaN(args[i])) return true; } if (args.length < 3) { // replace bogus hash return true; } else if (args.length == 3) { options.setCenterZoom(args); } } function move() { var s1 = options.getCenterZoom(); if (s0 !== s1) { s0 = s1; // don't recenter the map! pushState(s0); } } function stateChange(state) { // ignore spurious hashchange events if (state === s0) return; if (parseHash(s0 = state)) { // replace bogus hash move(); } } var _move = wax.u.throttle(move, 500); hash.add = function() { stateChange(getState()); options.bindChange(_move); return hash; }; hash.remove = function() { options.unbindChange(_move); return hash; }; return hash; }; wax = wax || {}; wax.interaction = function() { var gm = wax.gm(), interaction = {}, _downLock = false, _clickTimeout = null, // Active feature // Down event _d, // Touch tolerance tol = 4, grid, attach, detach, parent, map, tileGrid, // google maps sends touchmove and click at the same time // most of the time when an user taps the screen, see onUp // for more information _discardTouchMove = false; var defaultEvents = { mousemove: onMove, touchstart: onDown, mousedown: onDown }; var touchEnds = { touchend: onUp, touchmove: onUp, touchcancel: touchCancel }; var mspointerEnds = { MSPointerUp: onUp, MSPointerMove: onUp, MSPointerCancel: touchCancel }; var pointerEnds = { pointerup: onUp, pointermove: onUp, pointercancel: touchCancel }; // Abstract getTile method. Depends on a tilegrid with // grid[ [x, y, tile] ] structure. function getTile(e) { var g = grid(); var regExp = new RegExp(gm.tileRegexp()); for (var i = 0; i < g.length; i++) { if (e) { var isInside = ((g[i][0] <= e.y) && ((g[i][0] + 256) > e.y) && (g[i][1] <= e.x) && ((g[i][1] + 256) > e.x)); if(isInside && regExp.exec(g[i][2].src)) { return g[i][2]; } } } return false; } // Clear the double-click timeout to prevent double-clicks from // triggering popups. function killTimeout() { if (_clickTimeout) { window.clearTimeout(_clickTimeout); _clickTimeout = null; return true; } else { return false; } } function onMove(e) { // If the user is actually dragging the map, exit early // to avoid performance hits. if (_downLock) return; var _e = (e.type !== "MSPointerMove" && e.type !== "pointermove" ? e : e.originalEvent); var pos = wax.u.eventoffset(_e); interaction.screen_feature(pos, function(feature) { if (feature) { bean.fire(interaction, 'on', { parent: parent(), data: feature, formatter: gm.formatter().format, e: e }); } else { bean.fire(interaction, 'off'); } }); } // A handler for 'down' events - which means `mousedown` and `touchstart` function onDown(e) { // Prevent interaction offset calculations happening while // the user is dragging the map. // // Store this event so that we can compare it to the // up event _downLock = true; var _e = (e.type !== "MSPointerDown" && e.type !== "pointerdown" ? e : e.originalEvent); _d = wax.u.eventoffset(_e); if (e.type === 'mousedown') { bean.add(document.body, 'click', onUp); // track mouse up to remove lockDown when the drags end bean.add(document.body, 'mouseup', dragEnd); // Only track single-touches. Double-touches will not affect this // control } else if (e.type === 'touchstart' && e.touches.length === 1) { //GMaps fix: Because it's triggering always mousedown and click, we've to remove it bean.remove(document.body, 'click', onUp); //GMaps fix //When we finish dragging, then the click will be bean.add(document.body, 'click', onUp); bean.add(document.body, 'touchEnd', dragEnd); } else if (e.originalEvent.type === "MSPointerDown" && e.originalEvent.touches && e.originalEvent.touches.length === 1) { // Don't make the user click close if they hit another tooltip bean.fire(interaction, 'off'); // Touch moves invalidate touches bean.add(parent(), mspointerEnds); } else if (e.type === "pointerdown" && e.originalEvent.touches && e.originalEvent.touches.length === 1) { // Don't make the user click close if they hit another tooltip bean.fire(interaction, 'off'); // Touch moves invalidate touches bean.add(parent(), pointerEnds); } else { // Fix layer interaction in IE10/11 (CDBjs #139) // Reason: Internet Explorer is triggering pointerdown when you click on the marker, and other browsers don't. // Because of that, _downLock was active and it believed that you're dragging the map, instead of dragging the marker _downLock = false; } } function dragEnd() { _downLock = false; } function touchCancel() { bean.remove(parent(), touchEnds); bean.remove(parent(), mspointerEnds); bean.remove(parent(), pointerEnds); _downLock = false; } function onUp(e) { var evt = {}; var _e = (e.type !== "MSPointerMove" && e.type !== "MSPointerUp" && e.type !== "pointerup" && e.type !== "pointermove" ? e : e.originalEvent); var pos = wax.u.eventoffset(_e); _downLock = false; for (var key in _e) { evt[key] = _e[key]; } // for (var key in e) { // evt[key] = e[key]; // } bean.remove(document.body, 'mouseup', onUp); bean.remove(parent(), touchEnds); bean.remove(parent(), mspointerEnds); bean.remove(parent(), pointerEnds); if (e.type === 'touchend') { // If this was a touch and it survived, there's no need to avoid a double-tap // but also wax.u.eventoffset will have failed, since this touch // event doesn't have coordinates interaction.click(e, _d); } else if (pos && _d) { // If pos is not defined means wax can't calculate event position, // So next cases aren't possible. if (evt.type === "MSPointerMove" || evt.type === "MSPointerUp") { evt.changedTouches = []; interaction.click(evt, pos); } else if (evt.type === "pointermove" || evt.type === "pointerup") { interaction.click(evt, pos); } else if (Math.round(pos.y / tol) === Math.round(_d.y / tol) && Math.round(pos.x / tol) === Math.round(_d.x / tol)) { // if mousemove and click are sent at the same time this code // will not trigger click event because less than 150ms pass between // those events. // Because of that this flag discards touchMove if (_discardTouchMove && evt.type === 'touchmove') return onUp; // Contain the event data in a closure. // Ignore double-clicks by ignoring clicks within 300ms of // each other. if(!_clickTimeout) { _clickTimeout = window.setTimeout(function() { _clickTimeout = null; interaction.click(evt, pos); }, 150); } else { killTimeout(); } } } return onUp; } interaction.discardTouchMove = function(_) { if (!arguments.length) return _discardTouchMove; _discardTouchMove = _; return interaction; } // Handle a click event. Takes a second interaction.click = function(e, pos) { interaction.screen_feature(pos, function(feature) { if (feature) bean.fire(interaction, 'on', { parent: parent(), data: feature, formatter: gm.formatter().format, e: e }); }); }; interaction.screen_feature = function(pos, callback) { var tile = getTile(pos); if (!tile) callback(null); gm.getGrid(tile.src, function(err, g) { if (err || !g) return callback(null); var feature = g.tileFeature(pos.x, pos.y, tile); callback(feature); }); }; // set an attach function that should be // called when maps are set interaction.attach = function(x) { if (!arguments.length) return attach; attach = x; return interaction; }; interaction.detach = function(x) { if (!arguments.length) return detach; detach = x; return interaction; }; // Attach listeners to the map interaction.map = function(x) { if (!arguments.length) return map; map = x; if (attach) attach(map); bean.add(parent(), defaultEvents); bean.add(parent(), 'touchstart', onDown); bean.add(parent(), 'MSPointerDown', onDown); bean.add(parent(), 'pointerdown', onDown); return interaction; }; // set a grid getter for this control interaction.grid = function(x) { if (!arguments.length) return grid; grid = x; return interaction; }; // detach this and its events from the map cleanly interaction.remove = function(x) { if (detach) detach(map); bean.remove(parent(), defaultEvents); bean.fire(interaction, 'remove'); return interaction; }; // get or set a tilejson chunk of json interaction.tilejson = function(x) { if (!arguments.length) return gm.tilejson(); gm.tilejson(x); return interaction; }; // return the formatter, which has an exposed .format // function interaction.formatter = function() { return gm.formatter(); }; // ev can be 'on', 'off', fn is the handler interaction.on = function(ev, fn) { bean.add(interaction, ev, fn); return interaction; }; // ev can be 'on', 'off', fn is the handler interaction.off = function(ev, fn) { bean.remove(interaction, ev, fn); return interaction; }; // Return or set the gridmanager implementation interaction.gridmanager = function(x) { if (!arguments.length) return gm; gm = x; return interaction; }; // parent should be a function that returns // the parent element of the map interaction.parent = function(x) { parent = x; return interaction; }; return interaction; }; var wax = wax || {}; wax.location = function() { var t = {}; function on(o) { if ((o.e.type === 'mousemove' || !o.e.type)) { return; } else { var loc = o.formatter({ format: 'location' }, o.data); if (loc) { window.location.href = loc; } } } t.events = function() { return { on: on }; }; return t; }; // Wax GridUtil // ------------ // Wax header var wax = wax || {}; // Request // ------- // Request data cache. `callback(data)` where `data` is the response data. wax.request = { cache: {}, locks: {}, promises: {}, get: function(url, callback) { // Cache hit. if (this.cache[url]) { return callback(this.cache[url][0], this.cache[url][1]); // Cache miss. } else { this.promises[url] = this.promises[url] || []; this.promises[url].push(callback); // Lock hit. if (this.locks[url]) return; // Request. var that = this; this.locks[url] = true; reqwest({ url: url + (~url.indexOf('?') ? '&' : '?') + 'callback=grid', type: 'jsonp', jsonpCallback: 'callback', success: function(data) { that.locks[url] = false; that.cache[url] = [null, data]; for (var i = 0; i < that.promises[url].length; i++) { that.promises[url][i](that.cache[url][0], that.cache[url][1]); } }, error: function(err) { that.locks[url] = false; that.cache[url] = [err, null]; for (var i = 0; i < that.promises[url].length; i++) { that.promises[url][i](that.cache[url][0], that.cache[url][1]); } } }); } } }; // Templating // --------- wax.template = function(x) { var template = {}; // Clone the data object such that the '__[format]__' key is only // set for this instance of templating. template.format = function(options, data) { // mustache.js has been removed as a dependency throw new Error('mustache.js templates are no longer supported'); // var clone = {}; // for (var key in data) { // clone[key] = data[key]; // } // if (options.format) { // clone['__' + options.format + '__'] = true; // } // return wax.u.sanitize(Mustache.to_html(x, clone)); }; return template; }; if (!wax) var wax = {}; // A wrapper for reqwest jsonp to easily load TileJSON from a URL. wax.tilejson = function(url, callback) { reqwest({ url: url + (~url.indexOf('?') ? '&' : '?') + 'callback=grid', type: 'jsonp', jsonpCallback: 'callback', success: callback, error: callback }); }; var wax = wax || {}; // Utils are extracted from other libraries or // written from scratch to plug holes in browser compatibility. wax.u = { // From Bonzo offset: function(el) { // TODO: window margins // // Okay, so fall back to styles if offsetWidth and height are botched // by Firefox. var width = el.offsetWidth || parseInt(el.style.width, 10), height = el.offsetHeight || parseInt(el.style.height, 10), doc_body = document.body, top = 0, left = 0; var calculateOffset = function(el) { if (el === doc_body || el === document.documentElement) return; top += el.offsetTop; left += el.offsetLeft; var style = el.style.transform || el.style.WebkitTransform || el.style.OTransform || el.style.MozTransform || el.style.msTransform; if (style) { var match; if (match = style.match(/translate\((.+)px, (.+)px\)/)) { top += parseInt(match[2], 10); left += parseInt(match[1], 10); } else if (match = style.match(/translate3d\((.+)px, (.+)px, (.+)px\)/)) { top += parseInt(match[2], 10); left += parseInt(match[1], 10); } else if (match = style.match(/matrix3d\(([\-\d,\s]+)\)/)) { var pts = match[1].split(','); top += parseInt(pts[13], 10); left += parseInt(pts[12], 10); } else if (match = style.match(/matrix\(.+, .+, .+, .+, (.+), (.+)\)/)) { top += parseInt(match[2], 10); left += parseInt(match[1], 10); } } }; //Function that protects 'Unspected error' with Internet Explorer 11 function calculateOffsetIE(){ calculateOffset(el); try { while (el = el.offsetParent) { calculateOffset(el); } } catch(e) { // Hello, internet explorer. } } // from jquery, offset.js if ( typeof el.getBoundingClientRect !== "undefined" ) { var body = document.body; var doc = el.ownerDocument.documentElement; var clientTop = document.clientTop || body.clientTop || 0; var clientLeft = document.clientLeft || body.clientLeft || 0; var scrollTop = window.pageYOffset || doc.scrollTop; var scrollLeft = window.pageXOffset || doc.scrollLeft; //With Internet Explorer 11, the function getBoundingClientRect() sometimes //triggers the error: 'Unspected error.' Protecting it with try/catch try { var box = el.getBoundingClientRect(); top = box.top + scrollTop - clientTop; left = box.left + scrollLeft - clientLeft; } catch(e) { calculateOffsetIE(); } } else { calculateOffsetIE(); } // Offsets from the body top += doc_body.offsetTop; left += doc_body.offsetLeft; // Offsets from the HTML element top += doc_body.parentNode.offsetTop; left += doc_body.parentNode.offsetLeft; // Firefox and other weirdos. Similar technique to jQuery's // `doesNotIncludeMarginInBodyOffset`. var htmlComputed = document.defaultView ? window.getComputedStyle(doc_body.parentNode, null) : doc_body.parentNode.currentStyle; if (doc_body.parentNode.offsetTop !== parseInt(htmlComputed.marginTop, 10) && !isNaN(parseInt(htmlComputed.marginTop, 10))) { top += parseInt(htmlComputed.marginTop, 10); left += parseInt(htmlComputed.marginLeft, 10); } return { top: top, left: left, height: height, width: width }; }, '$': function(x) { return (typeof x === 'string') ? document.getElementById(x) : x; }, // From quirksmode: normalize the offset of an event from the top-left // of the page. eventoffset: function(e) { var posx = 0; var posy = 0; if (!e) { e = window.event; } if (e.type == "MSPointerMove" || e.type == "MSPointerDown" || e.type == "MSPointerUp") { return { x: e.pageX + window.pageXOffset, y: e.pageY + window.pageYOffset } } if (e.pageX || e.pageY) { // Good browsers return { x: e.pageX, y: e.pageY }; } else if (e.clientX || e.clientY) { // Internet Explorer return { x: e.clientX, y: e.clientY }; } else if (e.touches && e.touches.length === 1) { // Touch browsers return { x: e.touches[0].pageX, y: e.touches[0].pageY }; } }, // Ripped from underscore.js // Internal function used to implement `_.throttle` and `_.debounce`. limit: function(func, wait, debounce) { var timeout; return function() { var context = this, args = arguments; var throttler = function() { timeout = null; func.apply(context, args); }; if (debounce) clearTimeout(timeout); if (debounce || !timeout) timeout = setTimeout(throttler, wait); }; }, // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. throttle: function(func, wait) { return this.limit(func, wait, false); }, sanitize: function(content) { if (!content) return ''; function urlX(url) { // Data URIs are subject to a bug in Firefox // https://bugzilla.mozilla.org/show_bug.cgi?id=255107 // which let them be a vector. But WebKit does 'the right thing' // or at least 'something' about this situation, so we'll tolerate // them. if (/^(https?:\/\/|data:image)/.test(url)) { return url; } } function idX(id) { return id; } return html_sanitize(content, urlX, idX); } }; wax = wax || {}; wax.leaf = wax.leaf || {}; wax.leaf.hash = function(map) { return wax.hash({ getCenterZoom: function () { var center = map.getCenter(), zoom = map.getZoom(), precision = Math.max( 0, Math.ceil(Math.log(zoom) / Math.LN2)); return [ zoom, center.lat.toFixed(precision), center.lng.toFixed(precision) ].join('/'); }, setCenterZoom: function (args) { map.setView(new L.LatLng(args[1], args[2]), args[0]); }, bindChange: function (fn) { map.on('moveend', fn); }, unbindChange: function (fn) { map.off('moveend', fn); } }); }; wax = wax || {}; wax.leaf = wax.leaf || {}; wax.leaf.interaction = function() { var dirty = false, _grid, map; function setdirty() { dirty = true; } function grid() { // TODO: don't build for tiles outside of viewport // Touch interaction leads to intermediate //var zoomLayer = map.createOrGetLayer(Math.round(map.getZoom())); //?what is this doing? // Calculate a tile grid and cache it, by using the `.tiles` // element on this map. if (!dirty && _grid) { return _grid; } else { return (_grid = (function(layers) { var o = []; for (var layerId in layers) { // This only supports tiled layers if (layers[layerId]._tiles) { for (var tile in layers[layerId]._tiles) { var _tile = layers[layerId]._tiles[tile]; // avoid adding tiles without src, grid url can't be found for them if(_tile.src) { var offset = wax.u.offset(_tile); o.push([offset.top, offset.left, _tile]); } } } } return o; })(map._layers)); } } function attach(x) { if (!arguments.length) return map; map = x; var l = ['moveend']; for (var i = 0; i < l.length; i++) { map.on(l[i], setdirty); } } function detach(x) { if (!arguments.length) return map; map = x; var l = ['moveend']; for (var i = 0; i < l.length; i++) { map.off(l[i], setdirty); } } return wax.interaction() .attach(attach) .detach(detach) .parent(function() { return map._container; }) .grid(grid); }; wax = wax || {}; wax.leaf = wax.leaf || {}; wax.leaf.connector = L.TileLayer.extend({ initialize: function(options) { options = options || {}; options.minZoom = options.minzoom || 0; options.maxZoom = options.maxzoom || 22; L.TileLayer.prototype.initialize.call(this, options.tiles[0], options); } }); wax = wax || {}; wax.g = wax.g || {}; // Attribution // ----------- // Attribution wrapper for Google Maps. wax.g.attribution = function(map, tilejson) { tilejson = tilejson || {}; var a, // internal attribution control attribution = {}; attribution.element = function() { return a.element(); }; attribution.appendTo = function(elem) { wax.u.$(elem).appendChild(a.element()); return this; }; attribution.init = function() { a = wax.attribution(); a.content(tilejson.attribution); a.element().className = 'map-attribution map-g'; return this; }; return attribution.init(); }; wax = wax || {}; wax.g = wax.g || {}; // Bandwidth Detection // ------------------ wax.g.bwdetect = function(map, options) { options = options || {}; var lowpng = options.png || '.png128', lowjpg = options.jpg || '.jpg70'; // Create a low-bandwidth map type. if (!map.mapTypes['mb-low']) { var mb = map.mapTypes.mb; var tilejson = { tiles: [], scheme: mb.options.scheme, blankImage: mb.options.blankImage, minzoom: mb.minZoom, maxzoom: mb.maxZoom, name: mb.name, description: mb.description }; for (var i = 0; i < mb.options.tiles.length; i++) { tilejson.tiles.push(mb.options.tiles[i] .replace('.png', lowpng) .replace('.jpg', lowjpg)); } m.mapTypes.set('mb-low', new wax.g.connector(tilejson)); } return wax.bwdetect(options, function(bw) { map.setMapTypeId(bw ? 'mb' : 'mb-low'); }); }; wax = wax || {}; wax.g = wax.g || {}; wax.g.hash = function(map) { return wax.hash({ getCenterZoom: function() { var center = map.getCenter(), zoom = map.getZoom(), precision = Math.max( 0, Math.ceil(Math.log(zoom) / Math.LN2)); return [zoom.toFixed(2), center.lat().toFixed(precision), center.lng().toFixed(precision) ].join('/'); }, setCenterZoom: function setCenterZoom(args) { map.setCenter(new google.maps.LatLng(args[1], args[2])); map.setZoom(args[0]); }, bindChange: function(fn) { google.maps.event.addListener(map, 'idle', fn); }, unbindChange: function(fn) { google.maps.event.removeListener(map, 'idle', fn); } }); }; wax = wax || {}; wax.g = wax.g || {}; wax.g.interaction = function() { var dirty = false, _grid, map, interactionEnabled = false; var tileloadListener = null, idleListener = null; function setdirty() { dirty = true; } function grid() { // when interaction is enabled there should be grid tiles if (!dirty && _grid && (_grid.length > 0 || !interactionEnabled)) { return _grid; } else { _grid = []; var zoom = map.getZoom(); var mapOffset = wax.u.offset(map.getDiv()); var get = function(mapType) { if (!mapType || !mapType.interactive) return; interactionEnabled = true; for (var key in mapType.cache) { if (key.split('/')[0] != zoom) continue; var tileOffset = wax.u.offset(mapType.cache[key]); _grid.push([ tileOffset.top, tileOffset.left, mapType.cache[key] ]); } }; // Iterate over base mapTypes and overlayMapTypes. for (var i in map.mapTypes) get(map.mapTypes[i]); map.overlayMapTypes.forEach(get); } return _grid; } function attach(x) { if (!arguments.length) return map; map = x; tileloadListener = google.maps.event.addListener(map, 'tileloaded', setdirty); idleListener = google.maps.event.addListener(map, 'idle', setdirty); } function detach(x) { if(tileloadListener) google.maps.event.removeListener(tileloadListener); if(idleListener) google.maps.event.removeListener(idleListener); } return wax.interaction() .attach(attach) .detach(detach) .discardTouchMove(true) .parent(function() { return map.getDiv(); }) .grid(grid); }; // Wax for Google Maps API v3 // -------------------------- // Wax header var wax = wax || {}; wax.g = wax.g || {}; // Wax Google Maps MapType: takes an object of options in the form // // { // name: '', // filetype: '.png', // layerName: 'world-light', // alt: '', // zoomRange: [0, 18], // baseUrl: 'a url', // } wax.g.connector = function(options) { options = options || {}; this.options = { tiles: options.tiles, scheme: options.scheme || 'xyz', blankImage: options.blankImage || 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' }; this.opacity = options.opacity || 0; this.minZoom = options.minzoom || 0; this.maxZoom = options.maxzoom || 22; this.name = options.name || ''; this.description = options.description || ''; // non-configurable options this.interactive = true; this.tileSize = new google.maps.Size(256, 256); // DOM element cache this.cache = {}; }; // Get a tile element from a coordinate, zoom level, and an ownerDocument. wax.g.connector.prototype.getTile = function(coord, zoom, ownerDocument) { var key = zoom + '/' + coord.x + '/' + coord.y; if (!this.cache[key]) { var img = this.cache[key] = new Image(256, 256); this.cache[key].src = this.getTileUrl(coord, zoom); this.cache[key].setAttribute('gTileKey', key); this.cache[key].setAttribute("style","opacity: "+this.opacity+"; filter: alpha(opacity="+(this.opacity*100)+");"); this.cache[key].onerror = function() { img.style.display = 'none'; }; } return this.cache[key]; }; // Remove a tile that has fallen out of the map's viewport. // // TODO: expire cache data in the gridmanager. wax.g.connector.prototype.releaseTile = function(tile) { // var key = tile.getAttribute('gTileKey'); // if (this.cache[key]) delete this.cache[key]; // if (tile.parentNode) tile.parentNode.removeChild(tile); }; // Get a tile url, based on x, y coordinates and a z value. wax.g.connector.prototype.getTileUrl = function(coord, z) { // Y coordinate is flipped in Mapbox, compared to Google var mod = Math.pow(2, z), y = (this.options.scheme === 'tms') ? (mod - 1) - coord.y : coord.y, x = (coord.x % mod); x = (x < 0) ? (coord.x % mod) + mod : x; if (y < 0) return this.options.blankImage; return this.options.tiles [parseInt(x + y, 10) % this.options.tiles.length] .replace(/\{z\}/g, z) .replace(/\{x\}/g, x) .replace(/\{y\}/g, y); };