]> git.openstreetmap.org Git - rails.git/blobdiff - vendor/assets/leaflet/leaflet.js
Added helper function set_references for mail header
[rails.git] / vendor / assets / leaflet / leaflet.js
index 63f6fb790cac8c3a9674ea57f9b47edcf5611e5a..a845eb6aeeccd94d1cd79a2ac770d0c83c06f442 100644 (file)
@@ -1,6 +1,6 @@
 /* @preserve
- * Leaflet 1.3.1, a JS library for interactive maps. http://leafletjs.com
- * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade
+ * Leaflet 1.5.1+build.2e3e0ff, a JS library for interactive maps. http://leafletjs.com
+ * (c) 2010-2018 Vladimir Agafonkin, (c) 2010-2011 CloudMade
  */
 
 (function (global, factory) {
@@ -9,7 +9,7 @@
        (factory((global.L = {})));
 }(this, (function (exports) { 'use strict';
 
-var version = "1.3.1";
+var version = "1.5.1+build.2e3e0ffb";
 
 /*
  * @namespace Util
@@ -127,8 +127,8 @@ function falseFn() { return false; }
 // @function formatNum(num: Number, digits?: Number): Number
 // Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
 function formatNum(num, digits) {
-       var pow = Math.pow(10, (digits === undefined ? 6 : digits));
-       return Math.round(num * pow) / pow;
+       digits = (digits === undefined ? 6 : digits);
+       return +(Math.round(num + ('e+' + digits)) + ('e-' + digits));
 }
 
 // @function trim(str: String): String
@@ -468,7 +468,7 @@ var Events = {
         *
         * @alternative
         * @method off: this
-        * Removes all listeners to all events on the object.
+        * Removes all listeners to all events on the object. This includes implicitly attached events.
         */
        off: function (types, fn, context) {
 
@@ -1360,7 +1360,7 @@ function toLatLngBounds(a, b) {
  * map.panTo(L.latLng(50, 30));
  * ```
  *
- * Note that `LatLng` does not inherit from Leafet's `Class` object,
+ * Note that `LatLng` does not inherit from Leaflet's `Class` object,
  * which means new classes can't inherit from it, and new methods
  * can't be added to it with the `include` function.
  */
@@ -1648,9 +1648,11 @@ var Earth = extend({}, CRS, {
  * a sphere. Used by the `EPSG:3857` CRS.
  */
 
+var earthRadius = 6378137;
+
 var SphericalMercator = {
 
-       R: 6378137,
+       R: earthRadius,
        MAX_LATITUDE: 85.0511287798,
 
        project: function (latlng) {
@@ -1673,7 +1675,7 @@ var SphericalMercator = {
        },
 
        bounds: (function () {
-               var d = 6378137 * Math.PI;
+               var d = earthRadius * Math.PI;
                return new Bounds([-d, -d], [d, d]);
        })()
 };
@@ -1922,7 +1924,7 @@ var mobileOpera = mobile && opera;
 var mobileGecko = mobile && gecko;
 
 // @property retina: Boolean
-// `true` for browsers on a high-resolution "retina" screen.
+// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
 
 
@@ -2171,6 +2173,7 @@ function addDoubleTapListener(obj, handler, id) {
                                touch$$1 = newTouch;
                        }
                        touch$$1.type = 'dblclick';
+                       touch$$1.button = 0;
                        handler(touch$$1);
                        last = null;
                }
@@ -2207,671 +2210,703 @@ function removeDoubleTapListener(obj, id) {
 }
 
 /*
- * @namespace DomEvent
- * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
+ * @namespace DomUtil
+ *
+ * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
+ * tree, used by Leaflet internally.
+ *
+ * Most functions expecting or returning a `HTMLElement` also work for
+ * SVG elements. The only difference is that classes refer to CSS classes
+ * in HTML and SVG classes in SVG.
  */
 
-// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
-
-// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
-// Adds a listener function (`fn`) to a particular DOM event type of the
-// element `el`. You can optionally specify the context of the listener
-// (object the `this` keyword will point to). You can also pass several
-// space-separated types (e.g. `'click dblclick'`).
-
-// @alternative
-// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
-// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
-function on(obj, types, fn, context) {
 
-       if (typeof types === 'object') {
-               for (var type in types) {
-                       addOne(obj, type, types[type], fn);
-               }
-       } else {
-               types = splitWords(types);
+// @property TRANSFORM: String
+// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
+var TRANSFORM = testProp(
+       ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
 
-               for (var i = 0, len = types.length; i < len; i++) {
-                       addOne(obj, types[i], fn, context);
-               }
-       }
+// webkitTransition comes first because some browser versions that drop vendor prefix don't do
+// the same for the transitionend event, in particular the Android 4.1 stock browser
 
-       return this;
-}
+// @property TRANSITION: String
+// Vendor-prefixed transition style name.
+var TRANSITION = testProp(
+       ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
 
-var eventsKey = '_leaflet_events';
+// @property TRANSITION_END: String
+// Vendor-prefixed transitionend event name.
+var TRANSITION_END =
+       TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
 
-// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
-// Removes a previously added listener function.
-// Note that if you passed a custom context to on, you must pass the same
-// context to `off` in order to remove the listener.
 
-// @alternative
-// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
-// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
-function off(obj, types, fn, context) {
+// @function get(id: String|HTMLElement): HTMLElement
+// Returns an element given its DOM id, or returns the element itself
+// if it was passed directly.
+function get(id) {
+       return typeof id === 'string' ? document.getElementById(id) : id;
+}
 
-       if (typeof types === 'object') {
-               for (var type in types) {
-                       removeOne(obj, type, types[type], fn);
-               }
-       } else if (types) {
-               types = splitWords(types);
+// @function getStyle(el: HTMLElement, styleAttrib: String): String
+// Returns the value for a certain style attribute on an element,
+// including computed values or values set through CSS.
+function getStyle(el, style) {
+       var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
 
-               for (var i = 0, len = types.length; i < len; i++) {
-                       removeOne(obj, types[i], fn, context);
-               }
-       } else {
-               for (var j in obj[eventsKey]) {
-                       removeOne(obj, j, obj[eventsKey][j]);
-               }
-               delete obj[eventsKey];
+       if ((!value || value === 'auto') && document.defaultView) {
+               var css = document.defaultView.getComputedStyle(el, null);
+               value = css ? css[style] : null;
        }
-
-       return this;
+       return value === 'auto' ? null : value;
 }
 
-function addOne(obj, type, fn, context) {
-       var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
-
-       if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
-
-       var handler = function (e) {
-               return fn.call(context || obj, e || window.event);
-       };
-
-       var originalHandler = handler;
-
-       if (pointer && type.indexOf('touch') === 0) {
-               // Needs DomEvent.Pointer.js
-               addPointerListener(obj, type, handler, id);
-
-       } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
-                  !(pointer && chrome)) {
-               // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
-               // See #5180
-               addDoubleTapListener(obj, handler, id);
-
-       } else if ('addEventListener' in obj) {
-
-               if (type === 'mousewheel') {
-                       obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
-
-               } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
-                       handler = function (e) {
-                               e = e || window.event;
-                               if (isExternalTarget(obj, e)) {
-                                       originalHandler(e);
-                               }
-                       };
-                       obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
-
-               } else {
-                       if (type === 'click' && android) {
-                               handler = function (e) {
-                                       filterClick(e, originalHandler);
-                               };
-                       }
-                       obj.addEventListener(type, handler, false);
-               }
+// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
+// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
+function create$1(tagName, className, container) {
+       var el = document.createElement(tagName);
+       el.className = className || '';
 
-       } else if ('attachEvent' in obj) {
-               obj.attachEvent('on' + type, handler);
+       if (container) {
+               container.appendChild(el);
        }
-
-       obj[eventsKey] = obj[eventsKey] || {};
-       obj[eventsKey][id] = handler;
+       return el;
 }
 
-function removeOne(obj, type, fn, context) {
-
-       var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
-           handler = obj[eventsKey] && obj[eventsKey][id];
-
-       if (!handler) { return this; }
+// @function remove(el: HTMLElement)
+// Removes `el` from its parent element
+function remove(el) {
+       var parent = el.parentNode;
+       if (parent) {
+               parent.removeChild(el);
+       }
+}
 
-       if (pointer && type.indexOf('touch') === 0) {
-               removePointerListener(obj, type, id);
+// @function empty(el: HTMLElement)
+// Removes all of `el`'s children elements from `el`
+function empty(el) {
+       while (el.firstChild) {
+               el.removeChild(el.firstChild);
+       }
+}
 
-       } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
-                  !(pointer && chrome)) {
-               removeDoubleTapListener(obj, id);
+// @function toFront(el: HTMLElement)
+// Makes `el` the last child of its parent, so it renders in front of the other children.
+function toFront(el) {
+       var parent = el.parentNode;
+       if (parent && parent.lastChild !== el) {
+               parent.appendChild(el);
+       }
+}
 
-       } else if ('removeEventListener' in obj) {
+// @function toBack(el: HTMLElement)
+// Makes `el` the first child of its parent, so it renders behind the other children.
+function toBack(el) {
+       var parent = el.parentNode;
+       if (parent && parent.firstChild !== el) {
+               parent.insertBefore(el, parent.firstChild);
+       }
+}
 
-               if (type === 'mousewheel') {
-                       obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
+// @function hasClass(el: HTMLElement, name: String): Boolean
+// Returns `true` if the element's class attribute contains `name`.
+function hasClass(el, name) {
+       if (el.classList !== undefined) {
+               return el.classList.contains(name);
+       }
+       var className = getClass(el);
+       return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
+}
 
-               } else {
-                       obj.removeEventListener(
-                               type === 'mouseenter' ? 'mouseover' :
-                               type === 'mouseleave' ? 'mouseout' : type, handler, false);
+// @function addClass(el: HTMLElement, name: String)
+// Adds `name` to the element's class attribute.
+function addClass(el, name) {
+       if (el.classList !== undefined) {
+               var classes = splitWords(name);
+               for (var i = 0, len = classes.length; i < len; i++) {
+                       el.classList.add(classes[i]);
                }
-
-       } else if ('detachEvent' in obj) {
-               obj.detachEvent('on' + type, handler);
+       } else if (!hasClass(el, name)) {
+               var className = getClass(el);
+               setClass(el, (className ? className + ' ' : '') + name);
        }
-
-       obj[eventsKey][id] = null;
 }
 
-// @function stopPropagation(ev: DOMEvent): this
-// Stop the given event from propagation to parent elements. Used inside the listener functions:
-// ```js
-// L.DomEvent.on(div, 'click', function (ev) {
-//     L.DomEvent.stopPropagation(ev);
-// });
-// ```
-function stopPropagation(e) {
-
-       if (e.stopPropagation) {
-               e.stopPropagation();
-       } else if (e.originalEvent) {  // In case of Leaflet event.
-               e.originalEvent._stopped = true;
+// @function removeClass(el: HTMLElement, name: String)
+// Removes `name` from the element's class attribute.
+function removeClass(el, name) {
+       if (el.classList !== undefined) {
+               el.classList.remove(name);
        } else {
-               e.cancelBubble = true;
+               setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
        }
-       skipped(e);
-
-       return this;
 }
 
-// @function disableScrollPropagation(el: HTMLElement): this
-// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
-function disableScrollPropagation(el) {
-       addOne(el, 'mousewheel', stopPropagation);
-       return this;
+// @function setClass(el: HTMLElement, name: String)
+// Sets the element's class.
+function setClass(el, name) {
+       if (el.className.baseVal === undefined) {
+               el.className = name;
+       } else {
+               // in case of SVG element
+               el.className.baseVal = name;
+       }
 }
 
-// @function disableClickPropagation(el: HTMLElement): this
-// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
-// `'mousedown'` and `'touchstart'` events (plus browser variants).
-function disableClickPropagation(el) {
-       on(el, 'mousedown touchstart dblclick', stopPropagation);
-       addOne(el, 'click', fakeStop);
-       return this;
+// @function getClass(el: HTMLElement): String
+// Returns the element's class.
+function getClass(el) {
+       // Check if the element is an SVGElementInstance and use the correspondingElement instead
+       // (Required for linked SVG elements in IE11.)
+       if (el.correspondingElement) {
+               el = el.correspondingElement;
+       }
+       return el.className.baseVal === undefined ? el.className : el.className.baseVal;
 }
 
-// @function preventDefault(ev: DOMEvent): this
-// Prevents the default action of the DOM Event `ev` from happening (such as
-// following a link in the href of the a element, or doing a POST request
-// with page reload when a `<form>` is submitted).
-// Use it inside listener functions.
-function preventDefault(e) {
-       if (e.preventDefault) {
-               e.preventDefault();
-       } else {
-               e.returnValue = false;
+// @function setOpacity(el: HTMLElement, opacity: Number)
+// Set the opacity of an element (including old IE support).
+// `opacity` must be a number from `0` to `1`.
+function setOpacity(el, value) {
+       if ('opacity' in el.style) {
+               el.style.opacity = value;
+       } else if ('filter' in el.style) {
+               _setOpacityIE(el, value);
        }
-       return this;
 }
 
-// @function stop(ev: DOMEvent): this
-// Does `stopPropagation` and `preventDefault` at the same time.
-function stop(e) {
-       preventDefault(e);
-       stopPropagation(e);
-       return this;
-}
+function _setOpacityIE(el, value) {
+       var filter = false,
+           filterName = 'DXImageTransform.Microsoft.Alpha';
 
-// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
-// Gets normalized mouse position from a DOM event relative to the
-// `container` or to the whole page if not specified.
-function getMousePosition(e, container) {
-       if (!container) {
-               return new Point(e.clientX, e.clientY);
+       // filters collection throws an error if we try to retrieve a filter that doesn't exist
+       try {
+               filter = el.filters.item(filterName);
+       } catch (e) {
+               // don't set opacity to 1 if we haven't already set an opacity,
+               // it isn't needed and breaks transparent pngs.
+               if (value === 1) { return; }
        }
 
-       var rect = container.getBoundingClientRect();
+       value = Math.round(value * 100);
 
-       var scaleX = rect.width / container.offsetWidth || 1;
-       var scaleY = rect.height / container.offsetHeight || 1;
-       return new Point(
-               e.clientX / scaleX - rect.left - container.clientLeft,
-               e.clientY / scaleY - rect.top - container.clientTop);
+       if (filter) {
+               filter.Enabled = (value !== 100);
+               filter.Opacity = value;
+       } else {
+               el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
+       }
 }
 
-// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
-// and Firefox scrolls device pixels, not CSS pixels
-var wheelPxFactor =
-       (win && chrome) ? 2 * window.devicePixelRatio :
-       gecko ? window.devicePixelRatio : 1;
+// @function testProp(props: String[]): String|false
+// Goes through the array of style names and returns the first name
+// that is a valid style name for an element. If no such name is found,
+// it returns false. Useful for vendor-prefixed styles like `transform`.
+function testProp(props) {
+       var style = document.documentElement.style;
 
-// @function getWheelDelta(ev: DOMEvent): Number
-// Gets normalized wheel delta from a mousewheel DOM event, in vertical
-// pixels scrolled (negative if scrolling down).
-// Events from pointing devices without precise scrolling are mapped to
-// a best guess of 60 pixels.
-function getWheelDelta(e) {
-       return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
-              (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
-              (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
-              (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
-              (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
-              e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
-              (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
-              e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
-              0;
+       for (var i = 0; i < props.length; i++) {
+               if (props[i] in style) {
+                       return props[i];
+               }
+       }
+       return false;
 }
 
-var skipEvents = {};
+// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
+// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
+// and optionally scaled by `scale`. Does not have an effect if the
+// browser doesn't support 3D CSS transforms.
+function setTransform(el, offset, scale) {
+       var pos = offset || new Point(0, 0);
 
-function fakeStop(e) {
-       // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
-       skipEvents[e.type] = true;
+       el.style[TRANSFORM] =
+               (ie3d ?
+                       'translate(' + pos.x + 'px,' + pos.y + 'px)' :
+                       'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
+               (scale ? ' scale(' + scale + ')' : '');
 }
 
-function skipped(e) {
-       var events = skipEvents[e.type];
-       // reset when checking, as it's only used in map container and propagates outside of the map
-       skipEvents[e.type] = false;
-       return events;
+// @function setPosition(el: HTMLElement, position: Point)
+// Sets the position of `el` to coordinates specified by `position`,
+// using CSS translate or top/left positioning depending on the browser
+// (used by Leaflet internally to position its layers).
+function setPosition(el, point) {
+
+       /*eslint-disable */
+       el._leaflet_pos = point;
+       /* eslint-enable */
+
+       if (any3d) {
+               setTransform(el, point);
+       } else {
+               el.style.left = point.x + 'px';
+               el.style.top = point.y + 'px';
+       }
 }
 
-// check if element really left/entered the event target (for mouseenter/mouseleave)
-function isExternalTarget(el, e) {
+// @function getPosition(el: HTMLElement): Point
+// Returns the coordinates of an element previously positioned with setPosition.
+function getPosition(el) {
+       // this method is only used for elements previously positioned using setPosition,
+       // so it's safe to cache the position for performance
 
-       var related = e.relatedTarget;
+       return el._leaflet_pos || new Point(0, 0);
+}
 
-       if (!related) { return true; }
+// @function disableTextSelection()
+// Prevents the user from generating `selectstart` DOM events, usually generated
+// when the user drags the mouse through a page with text. Used internally
+// by Leaflet to override the behaviour of any click-and-drag interaction on
+// the map. Affects drag interactions on the whole document.
 
-       try {
-               while (related && (related !== el)) {
-                       related = related.parentNode;
+// @function enableTextSelection()
+// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
+var disableTextSelection;
+var enableTextSelection;
+var _userSelect;
+if ('onselectstart' in document) {
+       disableTextSelection = function () {
+               on(window, 'selectstart', preventDefault);
+       };
+       enableTextSelection = function () {
+               off(window, 'selectstart', preventDefault);
+       };
+} else {
+       var userSelectProperty = testProp(
+               ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
+
+       disableTextSelection = function () {
+               if (userSelectProperty) {
+                       var style = document.documentElement.style;
+                       _userSelect = style[userSelectProperty];
+                       style[userSelectProperty] = 'none';
                }
-       } catch (err) {
-               return false;
-       }
-       return (related !== el);
+       };
+       enableTextSelection = function () {
+               if (userSelectProperty) {
+                       document.documentElement.style[userSelectProperty] = _userSelect;
+                       _userSelect = undefined;
+               }
+       };
 }
 
-var lastClick;
-
-// this is a horrible workaround for a bug in Android where a single touch triggers two click events
-function filterClick(e, handler) {
-       var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
-           elapsed = lastClick && (timeStamp - lastClick);
+// @function disableImageDrag()
+// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
+// for `dragstart` DOM events, usually generated when the user drags an image.
+function disableImageDrag() {
+       on(window, 'dragstart', preventDefault);
+}
 
-       // are they closer together than 500ms yet more than 100ms?
-       // Android typically triggers them ~300ms apart while multiple listeners
-       // on the same event should be triggered far faster;
-       // or check if click is simulated on the element, and if it is, reject any non-simulated events
+// @function enableImageDrag()
+// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
+function enableImageDrag() {
+       off(window, 'dragstart', preventDefault);
+}
 
-       if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
-               stop(e);
-               return;
+var _outlineElement;
+var _outlineStyle;
+// @function preventOutline(el: HTMLElement)
+// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
+// of the element `el` invisible. Used internally by Leaflet to prevent
+// focusable elements from displaying an outline when the user performs a
+// drag interaction on them.
+function preventOutline(element) {
+       while (element.tabIndex === -1) {
+               element = element.parentNode;
        }
-       lastClick = timeStamp;
+       if (!element.style) { return; }
+       restoreOutline();
+       _outlineElement = element;
+       _outlineStyle = element.style.outline;
+       element.style.outline = 'none';
+       on(window, 'keydown', restoreOutline);
+}
 
-       handler(e);
+// @function restoreOutline()
+// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
+function restoreOutline() {
+       if (!_outlineElement) { return; }
+       _outlineElement.style.outline = _outlineStyle;
+       _outlineElement = undefined;
+       _outlineStyle = undefined;
+       off(window, 'keydown', restoreOutline);
 }
 
+// @function getSizedParentNode(el: HTMLElement): HTMLElement
+// Finds the closest parent node which size (width and height) is not null.
+function getSizedParentNode(element) {
+       do {
+               element = element.parentNode;
+       } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
+       return element;
+}
 
+// @function getScale(el: HTMLElement): Object
+// Computes the CSS scale currently applied on the element.
+// Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
+// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
+function getScale(element) {
+       var rect = element.getBoundingClientRect(); // Read-only in old browsers.
 
+       return {
+               x: rect.width / element.offsetWidth || 1,
+               y: rect.height / element.offsetHeight || 1,
+               boundingClientRect: rect
+       };
+}
 
-var DomEvent = (Object.freeze || Object)({
-       on: on,
-       off: off,
-       stopPropagation: stopPropagation,
-       disableScrollPropagation: disableScrollPropagation,
-       disableClickPropagation: disableClickPropagation,
-       preventDefault: preventDefault,
-       stop: stop,
-       getMousePosition: getMousePosition,
-       getWheelDelta: getWheelDelta,
-       fakeStop: fakeStop,
-       skipped: skipped,
-       isExternalTarget: isExternalTarget,
-       addListener: on,
-       removeListener: off
+
+var DomUtil = (Object.freeze || Object)({
+       TRANSFORM: TRANSFORM,
+       TRANSITION: TRANSITION,
+       TRANSITION_END: TRANSITION_END,
+       get: get,
+       getStyle: getStyle,
+       create: create$1,
+       remove: remove,
+       empty: empty,
+       toFront: toFront,
+       toBack: toBack,
+       hasClass: hasClass,
+       addClass: addClass,
+       removeClass: removeClass,
+       setClass: setClass,
+       getClass: getClass,
+       setOpacity: setOpacity,
+       testProp: testProp,
+       setTransform: setTransform,
+       setPosition: setPosition,
+       getPosition: getPosition,
+       disableTextSelection: disableTextSelection,
+       enableTextSelection: enableTextSelection,
+       disableImageDrag: disableImageDrag,
+       enableImageDrag: enableImageDrag,
+       preventOutline: preventOutline,
+       restoreOutline: restoreOutline,
+       getSizedParentNode: getSizedParentNode,
+       getScale: getScale
 });
 
 /*
- * @namespace DomUtil
- *
- * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
- * tree, used by Leaflet internally.
- *
- * Most functions expecting or returning a `HTMLElement` also work for
- * SVG elements. The only difference is that classes refer to CSS classes
- * in HTML and SVG classes in SVG.
+ * @namespace DomEvent
+ * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
  */
 
+// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
 
-// @property TRANSFORM: String
-// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
-var TRANSFORM = testProp(
-       ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
-
-// webkitTransition comes first because some browser versions that drop vendor prefix don't do
-// the same for the transitionend event, in particular the Android 4.1 stock browser
+// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
+// Adds a listener function (`fn`) to a particular DOM event type of the
+// element `el`. You can optionally specify the context of the listener
+// (object the `this` keyword will point to). You can also pass several
+// space-separated types (e.g. `'click dblclick'`).
 
-// @property TRANSITION: String
-// Vendor-prefixed transition style name.
-var TRANSITION = testProp(
-       ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
+// @alternative
+// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
+// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
+function on(obj, types, fn, context) {
 
-// @property TRANSITION_END: String
-// Vendor-prefixed transitionend event name.
-var TRANSITION_END =
-       TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
+       if (typeof types === 'object') {
+               for (var type in types) {
+                       addOne(obj, type, types[type], fn);
+               }
+       } else {
+               types = splitWords(types);
 
+               for (var i = 0, len = types.length; i < len; i++) {
+                       addOne(obj, types[i], fn, context);
+               }
+       }
 
-// @function get(id: String|HTMLElement): HTMLElement
-// Returns an element given its DOM id, or returns the element itself
-// if it was passed directly.
-function get(id) {
-       return typeof id === 'string' ? document.getElementById(id) : id;
+       return this;
 }
 
-// @function getStyle(el: HTMLElement, styleAttrib: String): String
-// Returns the value for a certain style attribute on an element,
-// including computed values or values set through CSS.
-function getStyle(el, style) {
-       var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
+var eventsKey = '_leaflet_events';
 
-       if ((!value || value === 'auto') && document.defaultView) {
-               var css = document.defaultView.getComputedStyle(el, null);
-               value = css ? css[style] : null;
-       }
-       return value === 'auto' ? null : value;
-}
+// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
+// Removes a previously added listener function.
+// Note that if you passed a custom context to on, you must pass the same
+// context to `off` in order to remove the listener.
 
-// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
-// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
-function create$1(tagName, className, container) {
-       var el = document.createElement(tagName);
-       el.className = className || '';
+// @alternative
+// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
+// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
+function off(obj, types, fn, context) {
 
-       if (container) {
-               container.appendChild(el);
-       }
-       return el;
-}
+       if (typeof types === 'object') {
+               for (var type in types) {
+                       removeOne(obj, type, types[type], fn);
+               }
+       } else if (types) {
+               types = splitWords(types);
 
-// @function remove(el: HTMLElement)
-// Removes `el` from its parent element
-function remove(el) {
-       var parent = el.parentNode;
-       if (parent) {
-               parent.removeChild(el);
+               for (var i = 0, len = types.length; i < len; i++) {
+                       removeOne(obj, types[i], fn, context);
+               }
+       } else {
+               for (var j in obj[eventsKey]) {
+                       removeOne(obj, j, obj[eventsKey][j]);
+               }
+               delete obj[eventsKey];
        }
-}
 
-// @function empty(el: HTMLElement)
-// Removes all of `el`'s children elements from `el`
-function empty(el) {
-       while (el.firstChild) {
-               el.removeChild(el.firstChild);
-       }
+       return this;
 }
 
-// @function toFront(el: HTMLElement)
-// Makes `el` the last child of its parent, so it renders in front of the other children.
-function toFront(el) {
-       var parent = el.parentNode;
-       if (parent.lastChild !== el) {
-               parent.appendChild(el);
-       }
-}
+function addOne(obj, type, fn, context) {
+       var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
 
-// @function toBack(el: HTMLElement)
-// Makes `el` the first child of its parent, so it renders behind the other children.
-function toBack(el) {
-       var parent = el.parentNode;
-       if (parent.firstChild !== el) {
-               parent.insertBefore(el, parent.firstChild);
-       }
-}
+       if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
 
-// @function hasClass(el: HTMLElement, name: String): Boolean
-// Returns `true` if the element's class attribute contains `name`.
-function hasClass(el, name) {
-       if (el.classList !== undefined) {
-               return el.classList.contains(name);
+       var handler = function (e) {
+               return fn.call(context || obj, e || window.event);
+       };
+
+       var originalHandler = handler;
+
+       if (pointer && type.indexOf('touch') === 0) {
+               // Needs DomEvent.Pointer.js
+               addPointerListener(obj, type, handler, id);
+
+       } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
+                  !(pointer && chrome)) {
+               // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
+               // See #5180
+               addDoubleTapListener(obj, handler, id);
+
+       } else if ('addEventListener' in obj) {
+
+               if (type === 'mousewheel') {
+                       obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
+
+               } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
+                       handler = function (e) {
+                               e = e || window.event;
+                               if (isExternalTarget(obj, e)) {
+                                       originalHandler(e);
+                               }
+                       };
+                       obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
+
+               } else {
+                       if (type === 'click' && android) {
+                               handler = function (e) {
+                                       filterClick(e, originalHandler);
+                               };
+                       }
+                       obj.addEventListener(type, handler, false);
+               }
+
+       } else if ('attachEvent' in obj) {
+               obj.attachEvent('on' + type, handler);
        }
-       var className = getClass(el);
-       return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
+
+       obj[eventsKey] = obj[eventsKey] || {};
+       obj[eventsKey][id] = handler;
 }
 
-// @function addClass(el: HTMLElement, name: String)
-// Adds `name` to the element's class attribute.
-function addClass(el, name) {
-       if (el.classList !== undefined) {
-               var classes = splitWords(name);
-               for (var i = 0, len = classes.length; i < len; i++) {
-                       el.classList.add(classes[i]);
+function removeOne(obj, type, fn, context) {
+
+       var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
+           handler = obj[eventsKey] && obj[eventsKey][id];
+
+       if (!handler) { return this; }
+
+       if (pointer && type.indexOf('touch') === 0) {
+               removePointerListener(obj, type, id);
+
+       } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
+                  !(pointer && chrome)) {
+               removeDoubleTapListener(obj, id);
+
+       } else if ('removeEventListener' in obj) {
+
+               if (type === 'mousewheel') {
+                       obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
+
+               } else {
+                       obj.removeEventListener(
+                               type === 'mouseenter' ? 'mouseover' :
+                               type === 'mouseleave' ? 'mouseout' : type, handler, false);
                }
-       } else if (!hasClass(el, name)) {
-               var className = getClass(el);
-               setClass(el, (className ? className + ' ' : '') + name);
+
+       } else if ('detachEvent' in obj) {
+               obj.detachEvent('on' + type, handler);
        }
+
+       obj[eventsKey][id] = null;
 }
 
-// @function removeClass(el: HTMLElement, name: String)
-// Removes `name` from the element's class attribute.
-function removeClass(el, name) {
-       if (el.classList !== undefined) {
-               el.classList.remove(name);
+// @function stopPropagation(ev: DOMEvent): this
+// Stop the given event from propagation to parent elements. Used inside the listener functions:
+// ```js
+// L.DomEvent.on(div, 'click', function (ev) {
+//     L.DomEvent.stopPropagation(ev);
+// });
+// ```
+function stopPropagation(e) {
+
+       if (e.stopPropagation) {
+               e.stopPropagation();
+       } else if (e.originalEvent) {  // In case of Leaflet event.
+               e.originalEvent._stopped = true;
        } else {
-               setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
+               e.cancelBubble = true;
        }
+       skipped(e);
+
+       return this;
 }
 
-// @function setClass(el: HTMLElement, name: String)
-// Sets the element's class.
-function setClass(el, name) {
-       if (el.className.baseVal === undefined) {
-               el.className = name;
-       } else {
-               // in case of SVG element
-               el.className.baseVal = name;
-       }
+// @function disableScrollPropagation(el: HTMLElement): this
+// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
+function disableScrollPropagation(el) {
+       addOne(el, 'mousewheel', stopPropagation);
+       return this;
 }
 
-// @function getClass(el: HTMLElement): String
-// Returns the element's class.
-function getClass(el) {
-       return el.className.baseVal === undefined ? el.className : el.className.baseVal;
+// @function disableClickPropagation(el: HTMLElement): this
+// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
+// `'mousedown'` and `'touchstart'` events (plus browser variants).
+function disableClickPropagation(el) {
+       on(el, 'mousedown touchstart dblclick', stopPropagation);
+       addOne(el, 'click', fakeStop);
+       return this;
 }
 
-// @function setOpacity(el: HTMLElement, opacity: Number)
-// Set the opacity of an element (including old IE support).
-// `opacity` must be a number from `0` to `1`.
-function setOpacity(el, value) {
-       if ('opacity' in el.style) {
-               el.style.opacity = value;
-       } else if ('filter' in el.style) {
-               _setOpacityIE(el, value);
+// @function preventDefault(ev: DOMEvent): this
+// Prevents the default action of the DOM Event `ev` from happening (such as
+// following a link in the href of the a element, or doing a POST request
+// with page reload when a `<form>` is submitted).
+// Use it inside listener functions.
+function preventDefault(e) {
+       if (e.preventDefault) {
+               e.preventDefault();
+       } else {
+               e.returnValue = false;
        }
+       return this;
 }
 
-function _setOpacityIE(el, value) {
-       var filter = false,
-           filterName = 'DXImageTransform.Microsoft.Alpha';
+// @function stop(ev: DOMEvent): this
+// Does `stopPropagation` and `preventDefault` at the same time.
+function stop(e) {
+       preventDefault(e);
+       stopPropagation(e);
+       return this;
+}
 
-       // filters collection throws an error if we try to retrieve a filter that doesn't exist
-       try {
-               filter = el.filters.item(filterName);
-       } catch (e) {
-               // don't set opacity to 1 if we haven't already set an opacity,
-               // it isn't needed and breaks transparent pngs.
-               if (value === 1) { return; }
+// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
+// Gets normalized mouse position from a DOM event relative to the
+// `container` (border excluded) or to the whole page if not specified.
+function getMousePosition(e, container) {
+       if (!container) {
+               return new Point(e.clientX, e.clientY);
        }
 
-       value = Math.round(value * 100);
+       var scale = getScale(container),
+           offset = scale.boundingClientRect; // left and top  values are in page scale (like the event clientX/Y)
 
-       if (filter) {
-               filter.Enabled = (value !== 100);
-               filter.Opacity = value;
-       } else {
-               el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
-       }
+       return new Point(
+               // offset.left/top values are in page scale (like clientX/Y),
+               // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
+               (e.clientX - offset.left) / scale.x - container.clientLeft,
+               (e.clientY - offset.top) / scale.y - container.clientTop
+       );
 }
 
-// @function testProp(props: String[]): String|false
-// Goes through the array of style names and returns the first name
-// that is a valid style name for an element. If no such name is found,
-// it returns false. Useful for vendor-prefixed styles like `transform`.
-function testProp(props) {
-       var style = document.documentElement.style;
+// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
+// and Firefox scrolls device pixels, not CSS pixels
+var wheelPxFactor =
+       (win && chrome) ? 2 * window.devicePixelRatio :
+       gecko ? window.devicePixelRatio : 1;
 
-       for (var i = 0; i < props.length; i++) {
-               if (props[i] in style) {
-                       return props[i];
-               }
-       }
-       return false;
+// @function getWheelDelta(ev: DOMEvent): Number
+// Gets normalized wheel delta from a mousewheel DOM event, in vertical
+// pixels scrolled (negative if scrolling down).
+// Events from pointing devices without precise scrolling are mapped to
+// a best guess of 60 pixels.
+function getWheelDelta(e) {
+       return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
+              (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
+              (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
+              (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
+              (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
+              e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
+              (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
+              e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
+              0;
 }
 
-// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
-// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
-// and optionally scaled by `scale`. Does not have an effect if the
-// browser doesn't support 3D CSS transforms.
-function setTransform(el, offset, scale) {
-       var pos = offset || new Point(0, 0);
+var skipEvents = {};
 
-       el.style[TRANSFORM] =
-               (ie3d ?
-                       'translate(' + pos.x + 'px,' + pos.y + 'px)' :
-                       'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
-               (scale ? ' scale(' + scale + ')' : '');
+function fakeStop(e) {
+       // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
+       skipEvents[e.type] = true;
 }
 
-// @function setPosition(el: HTMLElement, position: Point)
-// Sets the position of `el` to coordinates specified by `position`,
-// using CSS translate or top/left positioning depending on the browser
-// (used by Leaflet internally to position its layers).
-function setPosition(el, point) {
-
-       /*eslint-disable */
-       el._leaflet_pos = point;
-       /* eslint-enable */
-
-       if (any3d) {
-               setTransform(el, point);
-       } else {
-               el.style.left = point.x + 'px';
-               el.style.top = point.y + 'px';
-       }
+function skipped(e) {
+       var events = skipEvents[e.type];
+       // reset when checking, as it's only used in map container and propagates outside of the map
+       skipEvents[e.type] = false;
+       return events;
 }
 
-// @function getPosition(el: HTMLElement): Point
-// Returns the coordinates of an element previously positioned with setPosition.
-function getPosition(el) {
-       // this method is only used for elements previously positioned using setPosition,
-       // so it's safe to cache the position for performance
-
-       return el._leaflet_pos || new Point(0, 0);
-}
+// check if element really left/entered the event target (for mouseenter/mouseleave)
+function isExternalTarget(el, e) {
 
-// @function disableTextSelection()
-// Prevents the user from generating `selectstart` DOM events, usually generated
-// when the user drags the mouse through a page with text. Used internally
-// by Leaflet to override the behaviour of any click-and-drag interaction on
-// the map. Affects drag interactions on the whole document.
+       var related = e.relatedTarget;
 
-// @function enableTextSelection()
-// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
-var disableTextSelection;
-var enableTextSelection;
-var _userSelect;
-if ('onselectstart' in document) {
-       disableTextSelection = function () {
-               on(window, 'selectstart', preventDefault);
-       };
-       enableTextSelection = function () {
-               off(window, 'selectstart', preventDefault);
-       };
-} else {
-       var userSelectProperty = testProp(
-               ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
+       if (!related) { return true; }
 
-       disableTextSelection = function () {
-               if (userSelectProperty) {
-                       var style = document.documentElement.style;
-                       _userSelect = style[userSelectProperty];
-                       style[userSelectProperty] = 'none';
-               }
-       };
-       enableTextSelection = function () {
-               if (userSelectProperty) {
-                       document.documentElement.style[userSelectProperty] = _userSelect;
-                       _userSelect = undefined;
+       try {
+               while (related && (related !== el)) {
+                       related = related.parentNode;
                }
-       };
+       } catch (err) {
+               return false;
+       }
+       return (related !== el);
 }
 
-// @function disableImageDrag()
-// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
-// for `dragstart` DOM events, usually generated when the user drags an image.
-function disableImageDrag() {
-       on(window, 'dragstart', preventDefault);
-}
+var lastClick;
 
-// @function enableImageDrag()
-// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
-function enableImageDrag() {
-       off(window, 'dragstart', preventDefault);
-}
+// this is a horrible workaround for a bug in Android where a single touch triggers two click events
+function filterClick(e, handler) {
+       var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
+           elapsed = lastClick && (timeStamp - lastClick);
 
-var _outlineElement;
-var _outlineStyle;
-// @function preventOutline(el: HTMLElement)
-// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
-// of the element `el` invisible. Used internally by Leaflet to prevent
-// focusable elements from displaying an outline when the user performs a
-// drag interaction on them.
-function preventOutline(element) {
-       while (element.tabIndex === -1) {
-               element = element.parentNode;
-       }
-       if (!element.style) { return; }
-       restoreOutline();
-       _outlineElement = element;
-       _outlineStyle = element.style.outline;
-       element.style.outline = 'none';
-       on(window, 'keydown', restoreOutline);
-}
+       // are they closer together than 500ms yet more than 100ms?
+       // Android typically triggers them ~300ms apart while multiple listeners
+       // on the same event should be triggered far faster;
+       // or check if click is simulated on the element, and if it is, reject any non-simulated events
 
-// @function restoreOutline()
-// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
-function restoreOutline() {
-       if (!_outlineElement) { return; }
-       _outlineElement.style.outline = _outlineStyle;
-       _outlineElement = undefined;
-       _outlineStyle = undefined;
-       off(window, 'keydown', restoreOutline);
+       if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
+               stop(e);
+               return;
+       }
+       lastClick = timeStamp;
+
+       handler(e);
 }
 
 
-var DomUtil = (Object.freeze || Object)({
-       TRANSFORM: TRANSFORM,
-       TRANSITION: TRANSITION,
-       TRANSITION_END: TRANSITION_END,
-       get: get,
-       getStyle: getStyle,
-       create: create$1,
-       remove: remove,
-       empty: empty,
-       toFront: toFront,
-       toBack: toBack,
-       hasClass: hasClass,
-       addClass: addClass,
-       removeClass: removeClass,
-       setClass: setClass,
-       getClass: getClass,
-       setOpacity: setOpacity,
-       testProp: testProp,
-       setTransform: setTransform,
-       setPosition: setPosition,
-       getPosition: getPosition,
-       disableTextSelection: disableTextSelection,
-       enableTextSelection: enableTextSelection,
-       disableImageDrag: disableImageDrag,
-       enableImageDrag: enableImageDrag,
-       preventOutline: preventOutline,
-       restoreOutline: restoreOutline
+
+
+var DomEvent = (Object.freeze || Object)({
+       on: on,
+       off: off,
+       stopPropagation: stopPropagation,
+       disableScrollPropagation: disableScrollPropagation,
+       disableClickPropagation: disableClickPropagation,
+       preventDefault: preventDefault,
+       stop: stop,
+       getMousePosition: getMousePosition,
+       getWheelDelta: getWheelDelta,
+       fakeStop: fakeStop,
+       skipped: skipped,
+       isExternalTarget: isExternalTarget,
+       addListener: on,
+       removeListener: off
 });
 
 /*
@@ -3086,6 +3121,13 @@ var Map = Evented.extend({
        initialize: function (id, options) { // (HTMLElement or String, Object)
                options = setOptions(this, options);
 
+               // Make sure to assign internal flags at the beginning,
+               // to avoid inconsistent state in some edge cases.
+               this._handlers = [];
+               this._layers = {};
+               this._zoomBoundLayers = {};
+               this._sizeChanged = true;
+
                this._initContainer(id);
                this._initLayout();
 
@@ -3106,11 +3148,6 @@ var Map = Evented.extend({
                        this.setView(toLatLng(options.center), options.zoom, {reset: true});
                }
 
-               this._handlers = [];
-               this._layers = {};
-               this._zoomBoundLayers = {};
-               this._sizeChanged = true;
-
                this.callInitHooks();
 
                // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
@@ -3469,6 +3506,51 @@ var Map = Evented.extend({
                return this;
        },
 
+       // @method panInside(latlng: LatLng, options?: options): this
+       // Pans the map the minimum amount to make the `latlng` visible. Use
+       // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
+       // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
+       // If `latlng` is already within the (optionally padded) display bounds,
+       // the map will not be panned.
+       panInside: function (latlng, options) {
+               options = options || {};
+
+               var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
+                   paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
+                   center = this.getCenter(),
+                   pixelCenter = this.project(center),
+                   pixelPoint = this.project(latlng),
+                   pixelBounds = this.getPixelBounds(),
+                   halfPixelBounds = pixelBounds.getSize().divideBy(2),
+                   paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
+
+               if (!paddedBounds.contains(pixelPoint)) {
+                       this._enforcingBounds = true;
+                       var diff = pixelCenter.subtract(pixelPoint),
+                           newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
+
+                       if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
+                               newCenter.x = pixelCenter.x - diff.x;
+                               if (diff.x > 0) {
+                                       newCenter.x += halfPixelBounds.x - paddingTL.x;
+                               } else {
+                                       newCenter.x -= halfPixelBounds.x - paddingBR.x;
+                               }
+                       }
+                       if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
+                               newCenter.y = pixelCenter.y - diff.y;
+                               if (diff.y > 0) {
+                                       newCenter.y += halfPixelBounds.y - paddingTL.y;
+                               } else {
+                                       newCenter.y -= halfPixelBounds.y - paddingBR.y;
+                               }
+                       }
+                       this.panTo(this.unproject(newCenter), options);
+                       this._enforcingBounds = false;
+               }
+               return this;
+       },
+
        // @method invalidateSize(options: Zoom/pan options): this
        // Checks if the map container size changed and updates the map if so â€”
        // call it after you've changed the map size dynamically, also animating
@@ -3616,7 +3698,7 @@ var Map = Evented.extend({
                var lat = pos.coords.latitude,
                    lng = pos.coords.longitude,
                    latlng = new LatLng(lat, lng),
-                   bounds = latlng.toBounds(pos.coords.accuracy),
+                   bounds = latlng.toBounds(pos.coords.accuracy * 2),
                    options = this._locateOptions;
 
                if (options.setView) {
@@ -3692,6 +3774,10 @@ var Map = Evented.extend({
                if (this._clearControlPos) {
                        this._clearControlPos();
                }
+               if (this._resizeRequest) {
+                       cancelAnimFrame(this._resizeRequest);
+                       this._resizeRequest = null;
+               }
 
                this._clearHandlers();
 
@@ -3776,7 +3862,7 @@ var Map = Evented.extend({
                        this.options.maxZoom;
        },
 
-       // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
+       // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
        // Returns the maximum zoom level on which the given bounds fit to the map
        // view in its entirety. If `inside` (optional) is set to `true`, the method
        // instead returns the minimum zoom level on which the map view fits into
@@ -4225,9 +4311,15 @@ var Map = Evented.extend({
                // this event. Also fired on mobile when the user holds a single touch
                // for a second (also called long press).
                // @event keypress: KeyboardEvent
-               // Fired when the user presses a key from the keyboard while the map is focused.
+               // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
+               // @event keydown: KeyboardEvent
+               // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
+               // the `keydown` event is fired for keys that produce a character value and for keys
+               // that do not produce a character value.
+               // @event keyup: KeyboardEvent
+               // Fired when the user releases a key from the keyboard while the map is focused.
                onOff(this._container, 'click dblclick mousedown mouseup ' +
-                       'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
+                       'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
 
                if (this.options.trackResize) {
                        onOff(window, 'resize', this._onResize, this);
@@ -4291,7 +4383,7 @@ var Map = Evented.extend({
 
                var type = e.type;
 
-               if (type === 'mousedown' || type === 'keypress') {
+               if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
                        // prevents outline when clicking on keyboard-focusable element
                        preventOutline(e.target || e.srcElement);
                }
@@ -4330,7 +4422,7 @@ var Map = Evented.extend({
                        originalEvent: e
                };
 
-               if (e.type !== 'keypress') {
+               if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
                        var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
                        data.containerPoint = isMarker ?
                                this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
@@ -4583,7 +4675,7 @@ var Map = Evented.extend({
                }
 
                // @event zoomanim: ZoomAnimEvent
-               // Fired on every frame of a zoom animation
+               // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
                this.fire('zoomanim', {
                        center: center,
                        zoom: zoom,
@@ -4701,6 +4793,8 @@ var Control = Class.extend({
                        corner.appendChild(container);
                }
 
+               this._map.on('unload', this.remove, this);
+
                return this;
        },
 
@@ -4717,6 +4811,7 @@ var Control = Class.extend({
                        this.onRemove(this._map);
                }
 
+               this._map.off('unload', this.remove, this);
                this._map = null;
 
                return this;
@@ -4939,13 +5034,13 @@ var Layers = Control.extend({
        // Expand the control container if collapsed.
        expand: function () {
                addClass(this._container, 'leaflet-control-layers-expanded');
-               this._form.style.height = null;
+               this._section.style.height = null;
                var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
-               if (acceptableHeight < this._form.clientHeight) {
-                       addClass(this._form, 'leaflet-control-layers-scrollbar');
-                       this._form.style.height = acceptableHeight + 'px';
+               if (acceptableHeight < this._section.clientHeight) {
+                       addClass(this._section, 'leaflet-control-layers-scrollbar');
+                       this._section.style.height = acceptableHeight + 'px';
                } else {
-                       removeClass(this._form, 'leaflet-control-layers-scrollbar');
+                       removeClass(this._section, 'leaflet-control-layers-scrollbar');
                }
                this._checkDisabledLayers();
                return this;
@@ -4969,7 +5064,7 @@ var Layers = Control.extend({
                disableClickPropagation(container);
                disableScrollPropagation(container);
 
-               var form = this._form = create$1('form', className + '-list');
+               var section = this._section = create$1('section', className + '-list');
 
                if (collapsed) {
                        this._map.on('click', this.collapse, this);
@@ -4997,11 +5092,11 @@ var Layers = Control.extend({
                        this.expand();
                }
 
-               this._baseLayersList = create$1('div', className + '-base', form);
-               this._separator = create$1('div', className + '-separator', form);
-               this._overlaysList = create$1('div', className + '-overlays', form);
+               this._baseLayersList = create$1('div', className + '-base', section);
+               this._separator = create$1('div', className + '-separator', section);
+               this._overlaysList = create$1('div', className + '-overlays', section);
 
-               container.appendChild(form);
+               container.appendChild(section);
        },
 
        _getLayer: function (id) {
@@ -5114,7 +5209,7 @@ var Layers = Control.extend({
                        input.className = 'leaflet-control-layers-selector';
                        input.defaultChecked = checked;
                } else {
-                       input = this._createRadioElement('leaflet-base-layers', checked);
+                       input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
                }
 
                this._layerControlInputs.push(input);
@@ -5338,6 +5433,10 @@ Map.mergeOptions({
 
 Map.addInitHook(function () {
        if (this.options.zoomControl) {
+               // @section Controls
+               // @property zoomControl: Control.Zoom
+               // The default zoom control (only available if the
+               // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
                this.zoomControl = new Zoom();
                this.addControl(this.zoomControl);
        }
@@ -5495,7 +5594,7 @@ var Attribution = Control.extend({
 
                // @option prefix: String = 'Leaflet'
                // The HTML text shown before the attributions. Pass `false` to disable.
-               prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
+               prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
        },
 
        initialize: function (options) {
@@ -5777,10 +5876,14 @@ var Draggable = Evented.extend({
                // Fired when a drag is about to start.
                this.fire('down');
 
-               var first = e.touches ? e.touches[0] : e;
+               var first = e.touches ? e.touches[0] : e,
+                   sizedParent = getSizedParentNode(this._element);
 
                this._startPoint = new Point(first.clientX, first.clientY);
 
+               // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
+               this._parentScale = getScale(sizedParent);
+
                on(document, MOVE[e.type], this._onMove, this);
                on(document, END[e.type], this._onUp, this);
        },
@@ -5799,12 +5902,17 @@ var Draggable = Evented.extend({
                }
 
                var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
-                   newPoint = new Point(first.clientX, first.clientY),
-                   offset = newPoint.subtract(this._startPoint);
+                   offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
 
                if (!offset.x && !offset.y) { return; }
                if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
 
+               // We assume that the parent container's position, border and scale do not change for the duration of the drag.
+               // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
+               // and we can use the cached value for the scale.
+               offset.x /= this._parentScale.x;
+               offset.y /= this._parentScale.y;
+
                preventDefault(e);
 
                if (!this._moved) {
@@ -6231,7 +6339,7 @@ var LonLat = {
  * @namespace Projection
  * @projection L.Projection.Mercator
  *
- * Elliptical Mercator projection â€” more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.
+ * Elliptical Mercator projection â€” more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
  */
 
 var Mercator = {
@@ -6391,7 +6499,7 @@ CRS.Simple = Simple;
  * @example
  *
  * ```js
- * var layer = L.Marker(latlng).addTo(map);
+ * var layer = L.marker(latlng).addTo(map);
  * layer.addTo(map);
  * layer.remove();
  * ```
@@ -6413,7 +6521,7 @@ var Layer = Evented.extend({
                pane: 'overlayPane',
 
                // @option attribution: String = null
-               // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
+               // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
                attribution: null,
 
                bubblingMouseEvents: true
@@ -6974,7 +7082,7 @@ var Icon = Class.extend({
 
        options: {
                popupAnchor: [0, 0],
-               tooltipAnchor: [0, 0],
+               tooltipAnchor: [0, 0]
        },
 
        initialize: function (options) {
@@ -7173,7 +7281,7 @@ var MarkerDrag = Handler.extend({
                    map = marker._map,
                    speed = this._marker.options.autoPanSpeed,
                    padding = this._marker.options.autoPanPadding,
-                   iconPos = L.DomUtil.getPosition(marker._icon),
+                   iconPos = getPosition(marker._icon),
                    bounds = map.getPixelBounds(),
                    origin = map.getPixelOrigin();
 
@@ -7197,7 +7305,7 @@ var MarkerDrag = Handler.extend({
                        this._draggable._newPos._add(movement);
                        this._draggable._startPos._add(movement);
 
-                       L.DomUtil.setPosition(marker._icon, this._draggable._newPos);
+                       setPosition(marker._icon, this._draggable._newPos);
                        this._onDrag(e);
 
                        this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
@@ -7229,7 +7337,7 @@ var MarkerDrag = Handler.extend({
        _onDrag: function (e) {
                var marker = this._marker,
                    shadow = marker._shadow,
-               iconPos = getPosition(marker._icon),
+                   iconPos = getPosition(marker._icon),
                    latlng = marker._map.layerPointToLatLng(iconPos);
 
                // update shadow position
@@ -7290,22 +7398,6 @@ var Marker = Layer.extend({
                // Option inherited from "Interactive layer" abstract class
                interactive: true,
 
-               // @option draggable: Boolean = false
-               // Whether the marker is draggable with mouse/touch or not.
-               draggable: false,
-
-               // @option autoPan: Boolean = false
-               // Set it to `true` if you want the map to do panning animation when marker hits the edges.
-               autoPan: false,
-
-               // @option autoPanPadding: Point = Point(50, 50)
-               // Equivalent of setting both top left and bottom right autopan padding to the same value.
-               autoPanPadding: [50, 50],
-
-               // @option autoPanSpeed: Number = 10
-               // Number of pixels the map should move by.
-               autoPanSpeed: 10,
-
                // @option keyboard: Boolean = true
                // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
                keyboard: true,
@@ -7338,10 +7430,32 @@ var Marker = Layer.extend({
                // `Map pane` where the markers icon will be added.
                pane: 'markerPane',
 
+               // @option pane: String = 'shadowPane'
+               // `Map pane` where the markers shadow will be added.
+               shadowPane: 'shadowPane',
+
                // @option bubblingMouseEvents: Boolean = false
                // When `true`, a mouse event on this marker will trigger the same event on the map
                // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
-               bubblingMouseEvents: false
+               bubblingMouseEvents: false,
+
+               // @section Draggable marker options
+               // @option draggable: Boolean = false
+               // Whether the marker is draggable with mouse/touch or not.
+               draggable: false,
+
+               // @option autoPan: Boolean = false
+               // Whether to pan the map when dragging this marker near its edge or not.
+               autoPan: false,
+
+               // @option autoPanPadding: Point = Point(50, 50)
+               // Distance (in pixels to the left/right and to the top/bottom) of the
+               // map edge to start panning the map.
+               autoPanPadding: [50, 50],
+
+               // @option autoPanSpeed: Number = 10
+               // Number of pixels the map should pan by.
+               autoPanSpeed: 10
        },
 
        /* @section
@@ -7412,6 +7526,12 @@ var Marker = Layer.extend({
                return this.update();
        },
 
+       // @method getIcon: Icon
+       // Returns the current icon used by the marker
+       getIcon: function () {
+               return this.options.icon;
+       },
+
        // @method setIcon(icon: Icon): this
        // Changes the marker icon.
        setIcon: function (icon) {
@@ -7507,7 +7627,7 @@ var Marker = Layer.extend({
                }
                this._initInteraction();
                if (newShadow && addShadow) {
-                       this.getPane('shadowPane').appendChild(this._shadow);
+                       this.getPane(options.shadowPane).appendChild(this._shadow);
                }
        },
 
@@ -7591,7 +7711,9 @@ var Marker = Layer.extend({
        _updateOpacity: function () {
                var opacity = this.options.opacity;
 
-               setOpacity(this._icon, opacity);
+               if (this._icon) {
+                       setOpacity(this._icon, opacity);
+               }
 
                if (this._shadow) {
                        setOpacity(this._shadow, opacity);
@@ -7728,6 +7850,9 @@ var Path = Layer.extend({
                setOptions(this, style);
                if (this._renderer) {
                        this._renderer._updateStyle(this);
+                       if (this.options.stroke && style.hasOwnProperty('weight')) {
+                               this._updateBounds();
+                       }
                }
                return this;
        },
@@ -8050,7 +8175,7 @@ var Polyline = Path.extend({
                return !this._latlngs.length;
        },
 
-       // @method closestLayerPoint: Point
+       // @method closestLayerPoint(p: Point): Point
        // Returns the point closest to `p` on the Polyline.
        closestLayerPoint: function (p) {
                var minDistance = Infinity,
@@ -8169,16 +8294,21 @@ var Polyline = Path.extend({
                this._rings = [];
                this._projectLatlngs(this._latlngs, this._rings, pxBounds);
 
-               var w = this._clickTolerance(),
-                   p = new Point(w, w);
-
                if (this._bounds.isValid() && pxBounds.isValid()) {
-                       pxBounds.min._subtract(p);
-                       pxBounds.max._add(p);
-                       this._pxBounds = pxBounds;
+                       this._rawPxBounds = pxBounds;
+                       this._updateBounds();
                }
        },
 
+       _updateBounds: function () {
+               var w = this._clickTolerance(),
+                   p = new Point(w, w);
+               this._pxBounds = new Bounds([
+                       this._rawPxBounds.min.subtract(p),
+                       this._rawPxBounds.max.add(p)
+               ]);
+       },
+
        // recursively turns latlngs into a set of rings with projected coordinates
        _projectLatlngs: function (latlngs, result, projectedBounds) {
                var flat = latlngs[0] instanceof LatLng,
@@ -8443,7 +8573,7 @@ var Polygon = Polyline.extend({
                var inside = false,
                    part, p1, p2, i, j, k, len, len2;
 
-               if (!this._pxBounds.contains(p)) { return false; }
+               if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
 
                // ray casting algorithm for detecting if point is in polygon
                for (i = 0, len = this._parts.length; i < len; i++) {
@@ -8606,10 +8736,10 @@ var GeoJSON = FeatureGroup.extend({
        },
 
        _setLayerStyle: function (layer, style) {
-               if (typeof style === 'function') {
-                       style = style(layer.feature);
-               }
                if (layer.setStyle) {
+                       if (typeof style === 'function') {
+                               style = style(layer.feature);
+                       }
                        layer.setStyle(style);
                }
        }
@@ -8759,19 +8889,25 @@ var PointToGeoJSON = {
 };
 
 // @namespace Marker
-// @method toGeoJSON(): Object
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
 Marker.include(PointToGeoJSON);
 
 // @namespace CircleMarker
-// @method toGeoJSON(): Object
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
 Circle.include(PointToGeoJSON);
 CircleMarker.include(PointToGeoJSON);
 
 
 // @namespace Polyline
-// @method toGeoJSON(): Object
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
 Polyline.include({
        toGeoJSON: function (precision) {
@@ -8787,7 +8923,9 @@ Polyline.include({
 });
 
 // @namespace Polygon
-// @method toGeoJSON(): Object
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
 Polygon.include({
        toGeoJSON: function (precision) {
@@ -8823,7 +8961,9 @@ LayerGroup.include({
                });
        },
 
-       // @method toGeoJSON(): Object
+       // @method toGeoJSON(precision?: Number): Object
+       // `precision` is the number of decimal places for coordinates.
+       // The default value is 6 places.
        // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
        toGeoJSON: function (precision) {
 
@@ -8870,7 +9010,7 @@ LayerGroup.include({
 // @namespace GeoJSON
 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
 // Creates a GeoJSON layer. Optionally accepts an object in
-// [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
+// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
 // (you can alternatively add it later with `addData` method) and an `options` object.
 function geoJSON(geojson, options) {
        return new GeoJSON(geojson, options);
@@ -8912,8 +9052,10 @@ var ImageOverlay = Layer.extend({
                // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
                interactive: false,
 
-               // @option crossOrigin: Boolean = false
-               // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
+               // @option crossOrigin: Boolean|String = false
+               // Whether the crossOrigin attribute will be added to the image.
+               // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
+               // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
                crossOrigin: false,
 
                // @option errorOverlayUrl: String = ''
@@ -8921,12 +9063,12 @@ var ImageOverlay = Layer.extend({
                errorOverlayUrl: '',
 
                // @option zIndex: Number = 1
-               // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the tile layer.
+               // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
                zIndex: 1,
 
                // @option className: String = ''
                // A custom class name to assign to the image. Empty by default.
-               className: '',
+               className: ''
        },
 
        initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
@@ -9032,7 +9174,7 @@ var ImageOverlay = Layer.extend({
                return events;
        },
 
-       // @method: setZIndex(value: Number) : this
+       // @method setZIndex(value: Number): this
        // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
        setZIndex: function (value) {
                this.options.zIndex = value;
@@ -9069,8 +9211,8 @@ var ImageOverlay = Layer.extend({
                img.onload = bind(this.fire, this, 'load');
                img.onerror = bind(this._overlayOnError, this, 'error');
 
-               if (this.options.crossOrigin) {
-                       img.crossOrigin = '';
+               if (this.options.crossOrigin || this.options.crossOrigin === '') {
+                       img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
                }
 
                if (this.options.zIndex) {
@@ -9118,7 +9260,7 @@ var ImageOverlay = Layer.extend({
 
        _overlayOnError: function () {
                // @event error: Event
-               // Fired when the ImageOverlay layer has loaded its image
+               // Fired when the ImageOverlay layer fails to load its image
                this.fire('error');
 
                var errorUrl = this.options.errorOverlayUrl;
@@ -9151,7 +9293,7 @@ var imageOverlay = function (url, bounds, options) {
  * ```js
  * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
  *     videoBounds = [[ 32, -130], [ 13, -100]];
- * L.VideoOverlay(videoUrl, videoBounds ).addTo(map);
+ * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
  * ```
  */
 
@@ -9166,7 +9308,12 @@ var VideoOverlay = ImageOverlay.extend({
 
                // @option loop: Boolean = true
                // Whether the video will loop back to the beginning when played.
-               loop: true
+               loop: true,
+
+               // @option keepAspectRatio: Boolean = true
+               // Whether the video will save aspect ratio after the projection.
+               // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
+               keepAspectRatio: true
        },
 
        _initImage: function () {
@@ -9196,6 +9343,7 @@ var VideoOverlay = ImageOverlay.extend({
 
                if (!isArray(this._url)) { this._url = [this._url]; }
 
+               if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; }
                vid.autoplay = !!this.options.autoplay;
                vid.loop = !!this.options.loop;
                for (var i = 0; i < this._url.length; i++) {
@@ -9219,6 +9367,49 @@ function videoOverlay(video, bounds, options) {
        return new VideoOverlay(video, bounds, options);
 }
 
+/*
+ * @class SVGOverlay
+ * @aka L.SVGOverlay
+ * @inherits ImageOverlay
+ *
+ * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
+ *
+ * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
+ *
+ * @example
+ *
+ * ```js
+ * var element = '<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="https://mdn.mozillademos.org/files/6457/mdn_logo_only_color.png" height="200" width="200"/></svg>',
+ *              elementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
+ * L.svgOverlay(element, elementBounds).addTo(map);
+ * ```
+ */
+
+var SVGOverlay = ImageOverlay.extend({
+       _initImage: function () {
+               var el = this._image = this._url;
+
+               addClass(el, 'leaflet-image-layer');
+               if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
+
+               el.onselectstart = falseFn;
+               el.onmousemove = falseFn;
+       }
+
+       // @method getElement(): SVGElement
+       // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
+       // used by this overlay.
+});
+
+
+// @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
+// Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
+// A viewBox attribute is required on the SVG element to zoom in and out properly.
+
+function svgOverlay(el, bounds, options) {
+       return new SVGOverlay(el, bounds, options);
+}
+
 /*
  * @class DivOverlay
  * @inherits Layer
@@ -9373,6 +9564,38 @@ var DivOverlay = Layer.extend({
                return this;
        },
 
+       _prepareOpen: function (parent, layer, latlng) {
+               if (!(layer instanceof Layer)) {
+                       latlng = layer;
+                       layer = parent;
+               }
+
+               if (layer instanceof FeatureGroup) {
+                       for (var id in parent._layers) {
+                               layer = parent._layers[id];
+                               break;
+                       }
+               }
+
+               if (!latlng) {
+                       if (layer.getCenter) {
+                               latlng = layer.getCenter();
+                       } else if (layer.getLatLng) {
+                               latlng = layer.getLatLng();
+                       } else {
+                               throw new Error('Unable to get source layer LatLng.');
+                       }
+               }
+
+               // set overlay source to this layer
+               this._source = layer;
+
+               // update the overlay (content, layout, ect...)
+               this.update();
+
+               return latlng;
+       },
+
        _updateContent: function () {
                if (!this._content) { return; }
 
@@ -9644,7 +9867,8 @@ var Popup = DivOverlay.extend({
        },
 
        _adjustPan: function () {
-               if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
+               if (!this.options.autoPan) { return; }
+               if (this._map._panAnim) { this._map._panAnim.stop(); }
 
                var map = this._map,
                    marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
@@ -9826,28 +10050,8 @@ Layer.include({
        // @method openPopup(latlng?: LatLng): this
        // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
        openPopup: function (layer, latlng) {
-               if (!(layer instanceof Layer)) {
-                       latlng = layer;
-                       layer = this;
-               }
-
-               if (layer instanceof FeatureGroup) {
-                       for (var id in this._layers) {
-                               layer = this._layers[id];
-                               break;
-                       }
-               }
-
-               if (!latlng) {
-                       latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
-               }
-
                if (this._popup && this._map) {
-                       // set popup source to this layer
-                       this._popup._source = layer;
-
-                       // update the popup (content, layout, ect...)
-                       this._popup.update();
+                       latlng = this._popup._prepareOpen(this, layer, latlng);
 
                        // open the popup on the map
                        this._map.openPopup(this._popup, latlng);
@@ -10244,29 +10448,8 @@ Layer.include({
        // @method openTooltip(latlng?: LatLng): this
        // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
        openTooltip: function (layer, latlng) {
-               if (!(layer instanceof Layer)) {
-                       latlng = layer;
-                       layer = this;
-               }
-
-               if (layer instanceof FeatureGroup) {
-                       for (var id in this._layers) {
-                               layer = this._layers[id];
-                               break;
-                       }
-               }
-
-               if (!latlng) {
-                       latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
-               }
-
                if (this._tooltip && this._map) {
-
-                       // set tooltip source to this layer
-                       this._tooltip._source = layer;
-
-                       // update the tooltip (content, layout, ect...)
-                       this._tooltip.update();
+                       latlng = this._tooltip._prepareOpen(this, layer, latlng);
 
                        // open the tooltip on the map
                        this._map.openTooltip(this._tooltip, latlng);
@@ -10377,8 +10560,9 @@ var DivIcon = Icon.extend({
                // iconAnchor: (Point),
                // popupAnchor: (Point),
 
-               // @option html: String = ''
-               // Custom HTML code to put inside the div element, empty by default.
+               // @option html: String|HTMLElement = ''
+               // Custom HTML code to put inside the div element, empty by default. Alternatively,
+               // an instance of `HTMLElement`.
                html: false,
 
                // @option bgPos: Point = [0, 0]
@@ -10392,7 +10576,12 @@ var DivIcon = Icon.extend({
                var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
                    options = this.options;
 
-               div.innerHTML = options.html !== false ? options.html : '';
+               if (options.html instanceof Element) {
+                       empty(div);
+                       div.appendChild(options.html);
+               } else {
+                       div.innerHTML = options.html !== false ? options.html : '';
+               }
 
                if (options.bgPos) {
                        var bgPos = toPoint(options.bgPos);
@@ -11178,12 +11367,6 @@ var GridLayer = Layer.extend({
                var tile = this._tiles[key];
                if (!tile) { return; }
 
-               // Cancels any pending http requests associated with the tile
-               // unless we're on Android's stock browser,
-               // see https://github.com/Leaflet/Leaflet/issues/137
-               if (!androidStock) {
-                       tile.el.setAttribute('src', emptyImageUrl);
-               }
                remove(tile.el);
 
                delete this._tiles[key];
@@ -11252,8 +11435,6 @@ var GridLayer = Layer.extend({
        },
 
        _tileReady: function (coords, err, tile) {
-               if (!this._map) { return; }
-
                if (err) {
                        // @event tileerror: TileErrorEvent
                        // Fired when there is an error loading a tile.
@@ -11343,12 +11524,12 @@ function gridLayer(options) {
  * @class TileLayer
  * @inherits GridLayer
  * @aka L.TileLayer
- * Used to load and display tile layers on the map. Extends `GridLayer`.
+ * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
  *
  * @example
  *
  * ```js
- * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
+ * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'}).addTo(map);
  * ```
  *
  * @section URL template
@@ -11407,8 +11588,10 @@ var TileLayer = GridLayer.extend({
                // If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
                detectRetina: false,
 
-               // @option crossOrigin: Boolean = false
-               // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
+               // @option crossOrigin: Boolean|String = false
+               // Whether the crossOrigin attribute will be added to the tiles.
+               // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
+               // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
                crossOrigin: false
        },
 
@@ -11446,7 +11629,13 @@ var TileLayer = GridLayer.extend({
 
        // @method setUrl(url: String, noRedraw?: Boolean): this
        // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
+       // If the URL does not change, the layer will not be redrawn unless
+       // the noRedraw parameter is set to false.
        setUrl: function (url, noRedraw) {
+               if (this._url === url && noRedraw === undefined) {
+                       noRedraw = true;
+               }
+
                this._url = url;
 
                if (!noRedraw) {
@@ -11465,8 +11654,8 @@ var TileLayer = GridLayer.extend({
                on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
                on(tile, 'error', bind(this._tileOnError, this, done, tile));
 
-               if (this.options.crossOrigin) {
-                       tile.crossOrigin = '';
+               if (this.options.crossOrigin || this.options.crossOrigin === '') {
+                       tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
                }
 
                /*
@@ -11567,6 +11756,28 @@ var TileLayer = GridLayer.extend({
                                }
                        }
                }
+       },
+
+       _removeTile: function (key) {
+               var tile = this._tiles[key];
+               if (!tile) { return; }
+
+               // Cancels any pending http requests associated with the tile
+               // unless we're on Android's stock browser,
+               // see https://github.com/Leaflet/Leaflet/issues/137
+               if (!androidStock) {
+                       tile.el.setAttribute('src', emptyImageUrl);
+               }
+
+               return GridLayer.prototype._removeTile.call(this, key);
+       },
+
+       _tileReady: function (coords, err, tile) {
+               if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
+                       return;
+               }
+
+               return GridLayer.prototype._tileReady.call(this, coords, err, tile);
        }
 });
 
@@ -11683,7 +11894,7 @@ var TileLayerWMS = TileLayer.extend({
                    bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
                    [min.y, min.x, max.y, max.x] :
                    [min.x, min.y, max.x, max.y]).join(','),
-               url = L.TileLayer.prototype.getTileUrl.call(this, coords);
+                   url = TileLayer.prototype.getTileUrl.call(this, coords);
                return url +
                        getParamString(this.wmsParams, url, this.options.uppercase) +
                        (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
@@ -11909,6 +12120,7 @@ var Canvas = Renderer.extend({
        },
 
        _destroyContainer: function () {
+               cancelAnimFrame(this._redrawRequest);
                delete this._ctx;
                remove(this._container);
                off(this._container);
@@ -11930,8 +12142,6 @@ var Canvas = Renderer.extend({
        _update: function () {
                if (this._map._animatingZoom && this._bounds) { return; }
 
-               this._drawnLayers = {};
-
                Renderer.prototype._update.call(this);
 
                var b = this._bounds,
@@ -12003,7 +12213,7 @@ var Canvas = Renderer.extend({
 
                delete layer._order;
 
-               delete this._layers[L.stamp(layer)];
+               delete this._layers[stamp(layer)];
 
                this._requestRedraw(layer);
        },
@@ -12025,14 +12235,20 @@ var Canvas = Renderer.extend({
        },
 
        _updateDashArray: function (layer) {
-               if (layer.options.dashArray) {
-                       var parts = layer.options.dashArray.split(','),
+               if (typeof layer.options.dashArray === 'string') {
+                       var parts = layer.options.dashArray.split(/[, ]+/),
                            dashArray = [],
+                           dashValue,
                            i;
                        for (i = 0; i < parts.length; i++) {
-                               dashArray.push(Number(parts[i]));
+                               dashValue = Number(parts[i]);
+                               // Ignore dash array containing invalid lengths
+                               if (isNaN(dashValue)) { return; }
+                               dashArray.push(dashValue);
                        }
                        layer.options._dashArray = dashArray;
+               } else {
+                       layer.options._dashArray = layer.options.dashArray;
                }
        },
 
@@ -12110,8 +12326,6 @@ var Canvas = Renderer.extend({
 
                if (!len) { return; }
 
-               this._drawnLayers[layer._leaflet_id] = layer;
-
                ctx.beginPath();
 
                for (i = 0; i < len; i++) {
@@ -12138,8 +12352,6 @@ var Canvas = Renderer.extend({
                    r = Math.max(Math.round(layer._radius), 1),
                    s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
 
-               this._drawnLayers[layer._leaflet_id] = layer;
-
                if (s !== 1) {
                        ctx.save();
                        ctx.scale(1, s);
@@ -12244,6 +12456,9 @@ var Canvas = Renderer.extend({
 
        _bringToFront: function (layer) {
                var order = layer._order;
+
+               if (!order) { return; }
+
                var next = order.next;
                var prev = order.prev;
 
@@ -12272,6 +12487,9 @@ var Canvas = Renderer.extend({
 
        _bringToBack: function (layer) {
                var order = layer._order;
+
+               if (!order) { return; }
+
                var next = order.next;
                var prev = order.prev;
 
@@ -12327,7 +12545,6 @@ var vmlCreate = (function () {
 /*
  * @class SVG
  *
- * Although SVG is not available on IE7 and IE8, these browsers support [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language), and the SVG renderer will fall back to VML in this case.
  *
  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
  * with old versions of Internet Explorer.
@@ -12669,10 +12886,7 @@ Map.include({
                var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
 
                if (!renderer) {
-                       // @namespace Map; @option preferCanvas: Boolean = false
-                       // Whether `Path`s should be rendered on a `Canvas` renderer.
-                       // By default, all `Path`s are rendered in a `SVG` renderer.
-                       renderer = this._renderer = (this.options.preferCanvas && canvas$1()) || svg$1();
+                       renderer = this._renderer = this._createRenderer();
                }
 
                if (!this.hasLayer(renderer)) {
@@ -12688,10 +12902,17 @@ Map.include({
 
                var renderer = this._paneRenderers[name];
                if (renderer === undefined) {
-                       renderer = (SVG && svg$1({pane: name})) || (Canvas && canvas$1({pane: name}));
+                       renderer = this._createRenderer({pane: name});
                        this._paneRenderers[name] = renderer;
                }
                return renderer;
+       },
+
+       _createRenderer: function (options) {
+               // @namespace Map; @option preferCanvas: Boolean = false
+               // Whether `Path`s should be rendered on a `Canvas` renderer.
+               // By default, all `Path`s are rendered in a `SVG` renderer.
+               return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
        }
 });
 
@@ -13326,20 +13547,18 @@ var Keyboard = Handler.extend({
                    offset;
 
                if (key in this._panKeys) {
+                       if (!map._panAnim || !map._panAnim._inProgress) {
+                               offset = this._panKeys[key];
+                               if (e.shiftKey) {
+                                       offset = toPoint(offset).multiplyBy(3);
+                               }
 
-                       if (map._panAnim && map._panAnim._inProgress) { return; }
-
-                       offset = this._panKeys[key];
-                       if (e.shiftKey) {
-                               offset = toPoint(offset).multiplyBy(3);
-                       }
-
-                       map.panBy(offset);
+                               map.panBy(offset);
 
-                       if (map.options.maxBounds) {
-                               map.panInsideBounds(map.options.maxBounds);
+                               if (map.options.maxBounds) {
+                                       map.panInsideBounds(map.options.maxBounds);
+                               }
                        }
-
                } else if (key in this._zoomKeys) {
                        map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
 
@@ -13707,21 +13926,9 @@ Map.ScrollWheelZoom = ScrollWheelZoom;
 Map.Tap = Tap;
 Map.TouchZoom = TouchZoom;
 
-// misc
-
-var oldL = window.L;
-function noConflict() {
-       window.L = oldL;
-       return this;
-}
-
-// Always export us to window global (see #2364)
-window.L = exports;
-
 Object.freeze = freeze;
 
 exports.version = version;
-exports.noConflict = noConflict;
 exports.Control = Control;
 exports.control = control;
 exports.Browser = Browser;
@@ -13764,6 +13971,8 @@ exports.ImageOverlay = ImageOverlay;
 exports.imageOverlay = imageOverlay;
 exports.VideoOverlay = VideoOverlay;
 exports.videoOverlay = videoOverlay;
+exports.SVGOverlay = SVGOverlay;
+exports.svgOverlay = svgOverlay;
 exports.DivOverlay = DivOverlay;
 exports.Popup = Popup;
 exports.popup = popup;
@@ -13798,5 +14007,14 @@ exports.rectangle = rectangle;
 exports.Map = Map;
 exports.map = createMap;
 
+var oldL = window.L;
+exports.noConflict = function() {
+       window.L = oldL;
+       return this;
+}
+
+// Always export us to window global (see #2364)
+window.L = exports;
+
 })));
 //# sourceMappingURL=leaflet-src.js.map